AriOS: how it came together.
How it came together.
- 01
Telegram as the universal capture surface
Capture began as iOS Shortcuts for photos and a Chrome extension for URLs: two surfaces, two auth flows, two failure modes. Telegram collapses both into one bot reachable from any phone, laptop, or iPad, with a native share sheet from every iOS app. A webhook on the Bot API, user ID allowlisted, turns it into a universal inbox for photos, voice notes, documents, and links, all routing through one /ingest endpoint. New surfaces ship as thin clients.
Backend Engineering The floor is the point. Each rung is faster and dumber than the one above it. The bottom rung never fails, which is what lets the top two be allowed to. - 02
The classifier became a provider ladder
Classification started heuristic-first because Ollama on a Dell CPU took 30 seconds to sort a 1500-char prompt into a 12-option enum. Moving the daemon onto a 4090 changed the economics: the same call is now warm in about a second, so the ladder runs Cloudflare first, local Ollama second, and the phrase-aware heuristic last. The heuristic still earns its place as the floor (about 80 percent accurate in under 50ms), which is what makes the chain safe to time out aggressively: nothing in the ingest path waits on a model that isn't answering.
AI & Machine Learning - 03
Vision moved to Cloudflare after every local tuning failed
Local llava-7b on the Dell was tuned every way available (sharper pre-resize, shorter prompts, thread pinning, longer keep-alive) to beat a 90s timeout; the floor stayed at 162s per image, because vision-token tokenisation dominates prompt-eval on CPU, not generation. Cloudflare Workers AI runs the same class of model on GPU, 10-15s warm round-trip, free tier covering 10k requests a day. Only the photo bytes leave the device for that step, never the vault, and it's one env var to move back local once a GPU mini-PC replaces the Dell.
AI & Machine Learning - 04
Perception in the cloud, the vault always local
Voice started as local Whisper-base ONNX inference via @xenova/transformers, slow enough on CPU that the bot needed a 'transcribing…' ack so the user knew it heard them. Cloudflare's Whisper turbo returns in two to five seconds, so it's now the default; local stays as the offline fallback, one env var to switch. Same rule as vision: only the audio leaves the device, never the vault.
AI & Machine Learning - 05
A webhook that survives the home box being down
The bot used to long-poll from a process on the Dell: a bad socket could strand it in a retry loop for hours, and a reboot at home took capture down with it. It's a Vercel-hosted webhook now, so there's no poll loop to wedge. The remaining failure mode, the home backend being unreachable, is handled by probe-and-queue: anything the webhook can't forward lands in a pending file that a drainer replays once the box answers again.
Backend Engineering - 06
Vault RAG on one retrieval index
One index sits beside the vault and matches three ways at once: on the words themselves, on what a passage means, and on what a saved image looks like, with the three sets of results merged into a single ranking. Ask the bot what you saved about some half-remembered trick and it searches the vault, answers from the passages it found, and cites them, or, when the local model is slow, drops to returning the sources without the prose rather than making you wait. Nothing is trapped in the index: it rebuilds from the markdown at any time, and the vault stays the source of truth.
Data Pipelines
Decisions on the record.
The few calls worth defending. Each one is a fork; the other branch would have been a different project.
Why hybrid retrieval, text and image together
Image-dense retrieval catches 'show me other things I've seen that look like this,' something most personal-knowledge tools skip. Pair it with semantic-text and keyword search so named entities stay findable, and fuse the three without a weight tournament.
The design system.
Tokens and treatments as they exist in the repo - values verbatim, nothing aspirational.
Constellation, adopted 2026-07-14 to replace a teal theme called Moonlight. Knowledge as a living star chart: dark mode is the sky, light mode is a printed atlas plate in cream paper and sepia ink, deliberately a different medium rather than an inverted palette, the way real 19th-century star atlases worked. Gold survives as the house palette rather than as the accent. Contrast here is not a review step, it is a build gate.
--void / --void-2#08080a / #0d0d10- Page ground in two steps. Neutral near-black, arrived at by correction: a warm #14120b has a real yellow cast that read as brown at dashboard density, and the second step exists so a card can recess below the ground rather than only rise above it.
--panel / --surface-sunk / --surface-raisedrgba(255,255,255, 0.035 / 0.012 / 0.055)- Three glass fills assigned by what a surface IS rather than by nesting: the default card, the supporting columns, the centre band. One fill for everything gave roughly twenty component families the same surface.
--line / --line-strongrgba(255,255,255, 0.07 / 0.14)- Hairlines. Panels separate by an edge catching light, not by a luminance jump.
--ink / -dim / -faint#f0f0f0 / rgba(240,240,240,0.66) / rgba(240,240,240,0.5)- One text colour at three alphas. --ink-faint is the token with the worst history in the file: it has failed WCAG AA twice.
--accent / --accent-dim#a9c7d9 / #7f96a3- A cool blue-white glass. It stayed spelled --teal through three repaints after it had stopped being teal, and was finally renamed.
Light theme#f7f7f4 ground, #ffffff panel, #26251e ink, #3c5c6e accent- The atlas plate. Surfaces go solid rather than alpha, and the ink alphas go HIGHER, because dark text on a light ground needs more alpha for the same legibility than light text on dark.
- Everything
AaBb Handgloves 0123
JetBrains Mono (--font-jbmono), then ui-monospace, SF Mono, Menlo--font-sans and --font-geo both resolve to --font-mono, so the tokens route every text role in the app to one monospace face. It is the single most distinctive decision in the system.
CSS custom properties in apps/ari-app/app/globals.css, with the light theme declared as a complete token set under :root[data-theme="light"] rather than as a patch over the dark one. The semantic token NAMES (--void, --ink, --line) were deliberately kept across the repaint so every existing component inherited the new palette without a per-file rename. Derived values use color-mix off --accent instead of new literals, so the glow, the wash and the component-family rule all follow the theme. pnpm a11y walks every surface in both themes and fails the build on anything under AA; it is what caught --ink-faint twice, and it is the adjudicator rather than the arithmetic.
- Glass as a primitive, blur as an exception
Every card carries an alpha fill, an alpha hairline, a specular top edge and a soft drop shadow. Real backdrop-filter blur is opt-in behind a .glass class, taken only by chrome that genuinely floats: nav, the command palette, the ask widget.
The parts of glassmorphism that actually read cost nothing. Blur is the part that costs a composited layer every frame, for an effect nobody sees through a solid page. Splitting the two lets the system be glassmorphic everywhere and expensive nowhere.
- A depth scale that runs downward
Supporting columns recess below the ground on --surface-sunk instead of the centre band being lifted further above it.
Lifting every surface to buy separation walks the whole app toward the 4.5:1 contrast floor, and the text token nearest that floor is the one that has already failed twice. Recessing buys the same separation against headroom nothing else is competing for.
- The component-family rule
--rule-mark is the accent at 55%, marking a card's top or left edge.
Enough to read as deliberate beside a plain hairline, not enough to compete with a full-strength accent on the controls sitting inside the same card. Derived from --accent rather than fixed, so it follows the theme.
Measuring a reference instead of describing one
The palette was rebuilt from cursor.com's computed styles rather than from an impression of them, which surfaced four things in order of impact: the ground is warm, the tonal steps between ground and raised surfaces are tiny so cards separate by a hairline, colour is almost absent, and the radius is 4px not 8px. Three of those survived. The warm ground did not: at dashboard density it read as brown, so the ground moved to a near-black closer to resend.com's.
Light as a different medium, not an inversion
Inverting a dark palette produces a light theme that feels like the dark one with the lamp on. The atlas plate is built the other way: surfaces become solid white instead of white alpha, the depth scale reverses so raised means the paper and sunk means the ground, and glass stops meaning a white sheen and starts meaning what a pane does over paper, at 0.72 rather than 0.05.
Keeping the token names through the repaint
Constellation changed every value and almost no names. --void, --ink and --line meant something different afterwards but were spelled the same, so every inline style and component in the app inherited the new palette with no migration. The two accent tokens were the exception that proves it: they stayed spelled teal through three repaints after they had stopped being teal, which is exactly the cost of naming a token after a colour instead of a role.
