Noggin — Architecture Specification v0.1
Purpose and scope
This specification turns the Noggin design document into something Claude Code can build from: the components, their interfaces, their data, and the order to build them in. It incorporates the all my noggins brand system v0.1 (tokens.css, tokens.json, logo/, the README) as the single source of visual truth for the website, the console and the app at talkto.allmynoggins.com.
How to use it with Claude Code
- This document lives in the repository as
docs/architecture.md, and the design handoff folder asdesign/(unchanged). - Each build milestone is a self-contained prompt under
prompts/. - Anything marked [decide] is an open point for the design consultation; Claude Code should not guess at it.
Out of scope for v0.1: Produce and Draft tools for voices (Read only in v0.1), voices talking to each other, acting on learned router weights, native iOS and Android apps, the voice marketplace, hosted brains until a compute provider is enabled in Ops.
System overview
| Component | What it is | Who runs it | Stack |
|---|---|---|---|
| Noggin (brain) | Single-owner agent engine: router and cost optimiser, model and speech adapters, personalities (voices), memory, feedback, audit, scheduler | The owner, on their hardware or a hosted container | Python 3.12, FastAPI, SQLite with sqlite-vec, one multi-arch container image |
| Console | <slug>.allmynoggins.com: open the app, manage noggins, create a noggin | The platform | Next.js on Cloudflare Pages, Postgres (Neon), Auth.js |
| Ops console | ops.allmynoggins.com: providers, tiers, accounts, fleet, templates, settings, audit | The platform (staff only) | Next.js, shares the console API and database |
| Noggin designer | designmynoggin.allmynoggins.com: guided creation of personalities | The platform (stateless) | React, shares packages/ui |
| talkto.allmynoggins.com | The mouthpiece: chat and talk PWA, paired directly to a brain; signs in with the console account to list and open noggins | Platform serves static files; runs in the owner’s browser | React, Vite, PWA, design tokens |
| Marketing site | allmynoggins.com landing, docs, build log | The platform | Astro on Cloudflare Pages, Markdown content |
Three layers (decided 2026-10-24)
The service is described in three layers; every boundary between them is a privacy boundary.
Figure: docs/architecture.svg (light theme values from design/tokens.css; rebuilt by hand when the layers change). The owner’s phone signs in at the console, is handed a single-use pairing token by the registry, and from then on talks to its noggin through that noggin’s own tunnel in every layer.
| Layer | What it is | Runs where | Holds |
|---|---|---|---|
| 1 · Platform | The site, talkto, the designer, the console (the owner’s management area: manage noggins, design my noggins, later billing and subscription), ops (users, noggin hosts, templates, service monitoring, later the billing gateway), and the registry | Entirely on Cloudflare: Pages for the static surfaces, Workers with D1 for the console, ops and the registry, Durable Objects for live state, Cron Triggers for probes | Accounts, pointers to noggins and noggin hosts, tunnels, templates, settings, counts. Never a message, memory, voice file or key |
| 2 · Managed compute | The noggin hosts we run: gpu-1 first, more later | Our own servers; each runs a node agent, the noggin containers it is asked to run (hosted, founder’s, demo), native Ollama and the shared speech services | Hosted owners’ noggin folders, for which the platform holds only a pointer. Shared engines process their prompts, which the hosted-tier copy says plainly |
| 3 · Self-run noggins | Noggins installed by their owners on their own laptop, Pi or VPS from an install package | The owner’s machine | Everything; we manage nothing and hold a pointer only if the owner’s noggin sends a heartbeat |
The registry (platform layer) is the only thing that connects the layers: it knows where each noggin is and whether it is online, hands the phone a single-use pairing token, makes each noggin’s tunnel, renders install packages, and tells node agents what to start and stop. It never carries a conversation: the phone talks to a noggin through that noggin’s own tunnel in every layer. An opt-in relay for noggins that cannot run a tunnel may be added later and will be labelled as such.
Node agent (managed compute): a small service on each noggin host that receives start, stop, move and destroy for a noggin from the registry, pulls the template image, reports capacity (RAM, VRAM, containers, disk) every 60 seconds, and nothing else. It has no access to a noggin folder’s contents beyond mounting it, and no route to another host. Placement is the registry’s: the host with room. Switch-over to a bigger host is export, import and start on the new host; the tunnel token lives in the folder, so the address never changes. A second host is the same shape with its own engines.
Billing (platform layer, later): a subscription pointer per account; the console’s billing & subscription and ops’s billing gateway are designed and not built. Provider: [decide] (Stripe if UK-first, a merchant of record if selling abroad from day one).
Milestones: 12 (shared speech services), 13 (console, ops and the registry to Workers and D1), 14 (node agent, the noggin-host adapter, hosted noggins behind the hosting flag).
Read it top down: the console opens the app and hands it a pairing token (or, signed in at talkto since milestone 10b, the app asks the console API for one itself); from then on the app talks to a brain directly. The console’s only link to a brain is management (create, pause, delete for hosted ones; an optional heartbeat from self-run ones). Only brains reach cloud providers, and only where a voice’s privacy level allows.
Repository layout (monorepo)
allmynoggins/
brain/ Noggin engine (Python)
app/ talkto.allmynoggins.com PWA (React); app/designer for the designer
console/ personal console and console API (Next.js)
ops/ staff console (Next.js)
site/ marketing site (Astro)
packages/ui/ shared React components built on the design tokens
design/ brand system handoff v0.1 (tokens, logos, README) — read-only
templates/ starter voices, brain profiles, install templates
docs/ this spec, the design doc, decisions, build log
prompts/ one prompt per milestone
infra/ account and server setup notes
packages/ui is the only place components are defined; console, ops, app and site import from it so the surfaces cannot drift.
Component 1: Noggin (brain)
Responsibilities: serve the brain API; route each request to a model and speech backend the voice permits; keep personalities, memory and the prompt library; log every outbound call; run scheduled jobs; export and import the whole folder or one personality.
Modules (brain/noggin/)
| Module | Does | Notes |
|---|---|---|
api/ | FastAPI routes, bearer-token auth, SSE streaming | Versioned under /v1/ |
router/ | Picks model and speech backends from host capability, voice privacy level, request class, memory context and feedback scores; records estimated and actual cost | Rules in rules.yaml; learned weights recorded, not acted on, in v0.1 |
adapters/llm/ | generate(messages, params) -> AsyncIterator[str] | ollama, anthropic, openai_compat (base URL configurable: Groq, Together, OpenAI) |
adapters/speech/ | transcribe(audio) -> AsyncIterator[str], speak(text) -> AsyncIterator[bytes] | whisper_local, kokoro_local, whisper_http and kokoro_http (shared services on a GPU server, milestone 12), elevenlabs, deepgram, openai_tts |
adapters/media/ | image, video, audio (v1.5) | Produce tier; files under voices/<slug>/media/ |
voices/ | Loads voices/*.md, validates front-matter, writes new voices; copies templates/ on first start | Schema in the Data section |
memory/ | Session history, scoped memory, prompt library, embeddings | SQLite plus sqlite-vec in memory.db |
redact/ | Masks names, emails, phone numbers, addresses and account numbers before a cloud call | Runs for asks voices |
audit/ | Writes every ModelRun, SpeechRun, ToolRun before the call is made | runs.db is append-only |
scheduler/ | Runs schedule entries from voice front-matter in-process through the same router and audit | Missed jobs fire at next start; asks cloud jobs wait for consent |
tools/read/ | search, fetch, calendar.read, mail.read | Read tier; behind the privacy gate |
transport/ | export and import of the encrypted archive; personality archives | Passphrase-derived key |
registry/ | Registers this brain’s address with the console and keeps a 60-second heartbeat (id, version, host capability, and this month’s spend, replies and main model per voice, not sent under e2e: required) | Optional; brain runs without it |
Router and cost optimiser
This is the module the design document calls the cost optimiser; it sits in front of every model and speech adapter and makes one decision per request.
(Diagram in the Claude Doc: inputs — request, voice policy, host capability, memory and feedback; decision order; outputs — local engines, runs.db, redaction and consent, cloud connectors.)
Decision order: 1. privacy gate, hard: local never goes out; 2. drop backends the host cannot run; 3. quality floor for the request class; 4. cheapest remaining, by estimated tokens; 5. same steps for speech backends.
Request classes in rules.yaml (v0.1): short_factual, conversation, long_reasoning, code, speech_stt, speech_tts. Each class names a minimum model tier (small local, large local, cloud) and a latency budget. The router classifies by prompt length, the presence of attachments or code, and the voice’s own hint. The estimate uses the provider’s published price per token and the tokenised prompt length; a ModelRun is committed with it before the call, and the actual figures replace it on completion. The monthly cost view and the per-reply cost line in the app both read runs.db.
Learning and the session adviser. Feedback and memory are router inputs. Every reply’s ModelRun is joined to its Feedback to give a per-voice, per-class score for quality (ratings), cost (actual spend) and performance (latency). The session adviser runs when a voice is opened: GET /v1/voices/{id}/advice returns three options, Performance, Quality and Cost, each with the model it would pick, expected cost per reply and typical latency from this owner’s history; the app shows them as a card and the choice becomes a session bias (POST /v1/sessions takes bias: performance | quality | cost). The privacy gate is applied before the bias, always. Built in v0.1: the scores and the adviser. The rest of this section is Continuous learning (decided 2026-10-24, milestone 12c).
Continuous learning (decided 2026-10-24; milestone 12c, not built)
Three loops, the first two inside the noggin and on by default, the third across noggins and on by default with an opt-out. None of them can widen the privacy gate, and none of them sends what the owner said anywhere.
1. Memory from every conversation (in the noggin, on by default). Every conversation is kept in memory.db, as now, with its feedback. When a session has been idle for 10 minutes or is closed, the noggin’s own local model (redaction.model, else default_local_model; never a cloud model, for every privacy level) reads it and writes up to five lasting facts into the voice’s memory scope (kind: learned, with the session id it came from). A fact whose embedding is within 0.9 cosine of an existing one updates that one instead of adding a duplicate. The run is audited as a ModelRun of kind remember before the call (invariant 4). The app lists what each voice remembers, with where it came from, and every item can be edited or deleted; deleting a session deletes the facts learned only from it. Parts of a conversation under Off the record are never stored, so never learned from. A voice file may set memory: manual to keep only what the owner saves by hand. Without a local model installed the step is skipped and the app says why.
2. The router learns from this owner (in the noggin, stage 2). Per voice and request class, once 20 ratings exist the learned score replaces the static quality floor from rules.yaml (a voice still never drops below its privacy gate or the host’s capability); the owner’s bias still applies. The app shows the learning maturity of each voice’s cost model: Learning with the count (12 of 20 ratings) below 20, Learning from your ratings from 20, and Fully mature at 50 ratings for a class. Before 20, the shared model (loop 3) is used as the starting point when it is on, then the owner’s own scores take over as they mature (weight n / 50, capped at 1). Learned weights live in memory.db, are listed in the app and can be reset per voice.
3. The shared cost-quality-performance model (across noggins, on by default, opt-out). Each noggin contributes engineered features only, never content: per reply, the model used (logical name and engine model), request class, host class (CPU or GPU, memory band), bands for tokens in and out, latency band, actual cost, the rating (up, down or none) and the session bias. Never: message text, memories, prompts, voice names or files, embeddings, names found by redaction, the owner’s account, the noggin’s id or name, or a time finer than the day. The noggin sums its features into daily buckets and sends one batch a day to the registry (POST /v1/learning/contributions, the registry endpoint the egress rule already allows), with a random id per batch and no noggin id; the registry adds the batch to totals per (model, class, host class) and discards the batch. A total is published only once at least 10 batches have contributed to it [decide: the minimum]. What comes back is the published table, fetched with the heartbeat: expected quality, latency and cost per model, class and host class. local voices contribute features too (owner’s decision); their label says so. The setting is in the app and the console (Help improve routing for everyone), on by default, and turning it off stops contributions at once and makes the noggin ignore the shared table. Before this is switched on in production: a legitimate-interests assessment and the privacy notice updated to name the features (owner, with advice), since usage data can be personal data under UK GDPR even without content.
What the owner is told (app, console and site, in these words or close): “Your voices learn from your conversations and your ratings, on your noggin, so conversations get richer and the right model answers for less. To help every noggin choose well, yours also shares anonymous numbers about each reply: which model, what kind of request, how long it took, what it cost and whether you rated it. Never what you said, what it remembers, or who you are. You can turn sharing off at any time.”
Optional intelligent classifier (v2): a small local model tags the request class before the rules run; off by default, a few hundred milliseconds per request, and never able to widen the privacy gate.
What it does not do in v0.1: act on learned weights (milestone 12c), split one prompt across several models, or cache responses.
Personalities. The unit the owner talks to is a personality: one voice file, its memory scope, its prompt library and its feedback record with learned weights, kept together under voices/<slug>/. The brain’s router, engines, audit log and scheduler are shared across all personalities in the noggin. POST /v1/voices/{id}/export produces a personality archive (the voice folder plus the Memory, Prompt, Feedback and learned-weight rows for its scope) that POST /v1/voices/import on any noggin can accept; this is distinct from whole-noggin export.
Host capability detection: on startup the brain reads RAM, GPU, installed engines and available models, publishes them at /v1/health, and the router narrows its choices to what is present. A voice that cannot run on this host is listed with status unavailable and a plain reason.
Exposure per noggin (decided 2026-10-17): each noggin runs a Cloudflare Tunnel sidecar (cloudflared) beside the brain container in the same compose project, and is published at https://<name>.allmynoggins.net. The brain shares the sidecar’s network and opens no port on the host; nothing is installed on the host but Docker, and phones need only a browser. Each noggin has its own tunnel; its token lives in the noggin folder (cloudflared/token), so the address moves with the folder, the token is covered by the encrypted export, and only one copy may run at a time. Other exposure values: lan (local network only) and public (the brain’s own certificate). Tailscale is not used anywhere in the product.
Trust in transit (decided 2026-10-17): Cloudflare terminates HTTPS at its edge, so it can technically read traffic between the app and a noggin, including message text and the bearer token. The owner accepts this by default. Milestone 05b adds an owner option for end-to-end encryption, WhatsApp-style: keys are agreed at pairing from the one-time code (never sent), message content travels encrypted between app and brain, and Cloudflare carries only ciphertext and metadata (which endpoint, when, how much). Built in 05b: e2e: off | on | required in config.yaml (off by default), SPAKE2 (RFC 9382) pairing, every request of an end-to-end device sealed inside POST /v1/e2e/call (so even the route and ids are hidden), talk over WS /v1/e2e/talk, and a safety number on both sides.
Because the brain is on the public internet, it treats every request as hostile: every endpoint except POST /v1/pair needs the bearer token; pairing codes last ten minutes, work once, and five wrong codes in ten minutes cancel all open codes; the API does not publish /docs or /openapi.json; CORS allows only the app’s origin.
Packaging: one Dockerfile producing a multi-arch image (linux/amd64, linux/arm64) published as ghcr.io/allmynoggins/noggin; one noggin binary built with PyInstaller for people without Docker. All state under the mounted noggin/ folder (/data in the container).
Component 2: Console and accounts
Responsibilities: sign-up and sign-in; claim a subdomain; keep pointers to the person’s noggins; issue pairing tokens; generate install packages; create, pause, export, move and delete hosted noggins; billing. It never proxies chat traffic and never reads a brain’s folder.
Routes (<slug>.allmynoggins.com)
| Route | Screen | Behaviour |
|---|---|---|
/ | My noggins | List with name, host, status (online, offline since <time>, paused, unavailable here), hosting tier, month-to-date cost, last export. Primary action per row: Open. |
/open/[id] | Mouthpiece | Fetches a short-lived pairing token, opens talkto.allmynoggins.com with the noggin and token in the URL fragment. After load, the console is out of the path. |
/new | Create a noggin | Starter templates, brain profile, host preset. Run it myself generates an install package for the chosen target; Host it for me is hidden until a compute provider is enabled in Ops. |
/noggins/[id] | Manage | Rename, pause, export (brain streams the archive to the browser), move (export, create elsewhere, import), delete (export offered first). Ownership statement beside the deletion controls. |
/account | Account | Sign-in methods, email, billing, delete account. |
Starter templates at creation. /new offers starter voices (career adviser, travel agent, reading coach, house and garden, study tutor), a brain profile (Private first, Balanced, Best quality) that pre-fills rules.yaml and default models, and a host preset. Templates are files under templates/ in the repo and are copied into the new noggin/ folder by the brain on first start.
Install package generator (console/api/installer/). POST /noggins/{id}/install-package with {target: laptop-macos | laptop-windows | laptop-linux | vps-linux | cloud-vm | gpu-server, exposure: tunnel | lan, ram_gb, name, port_offset} returns a zip rendered from templates/install/. Contents: install.sh or install.ps1 (idempotent: installs Docker and Ollama if absent, writes config, starts the service, prints pairing status), docker-compose.yml, noggin.env (single-use 5-minute pairing token, noggin id, console URL), cloudflared/token (this noggin’s tunnel token), config.yaml (brain profile, default models by ram_gb, exposure), README.md (platform steps, uninstall, ownership statement). Target extras: laptop adds the Docker Desktop link and native Ollama; vps-linux adds ufw deny-inbound, name and port offset; every target runs the brain with its cloudflared sidecar and a tunnel minted per noggin; cloud-vm emits a cloud-init.yaml user-data file that runs the same install.sh on first boot. No provider keys are ever in a package; the only secrets are the pairing token and the noggin’s tunnel token. A test renders every target and runs install.sh in a container with --dry-run.
Availability. Brains send a heartbeat (POST /noggins/register, every 60 s, opt-out in config.yaml) carrying id, version and host capability, and (milestone 10) this month’s figures per voice (name, privacy level, spend, replies, the model that answered most), which the console holds in its memory only, never in Postgres, shows only to the signed-in owner and forgets when the noggin goes offline; a brain with e2e: required does not send them. The console derives online, offline since <time> and unavailable here (per voice) and exposes them at GET /noggins; the app shows the same state in its header and mic button.
Authentication. Passkeys (WebAuthn) plus OpenID Connect sign-in with Google and Apple at launch, implemented with Auth.js on the console; further providers are configuration only. Scope is openid email; the console stores AuthMethod rows (account_id, kind: passkey | oidc, provider, subject, created_at) and nothing else from the provider. Sessions are HTTP-only cookies on *.allmynoggins.com (SameSite=Lax), each tied to a DeviceSession row (account_id, a browser-and-device label, created_at, last_seen_at) so Account → Signed-in devices can sign one browser out; a cookie works only while its row exists. talkto.allmynoggins.com uses the same cookie (milestone 10b): it is same-site with api.allmynoggins.com, which answers credentialed CORS for that one origin on three routes (list noggins, pairing token, sign out); the sign-in ceremony itself happens on my.allmynoggins.com and returns to the app. SSO identities never appear in pairing tokens or reach a brain. Account → Sign-in methods lets the owner add or remove any method, keeping at least one. “Continue with Google” and “Continue with Apple” use the brand system’s secondary button with the provider mark at 20 px.
Subdomain claim: [a-z0-9][a-z0-9-]{2,30}, reserved list for www, api, brains, docs, my, ops, talkto, designmynoggin. One per account in v0.1 [decide: vanity alias per noggin later].
Hosted noggin lifecycle: creating → pairing → online ↔ paused → deleting. Each hosted noggin is one machine with one volume at the enabled provider; the console talks to the provider through a small worker so credentials never reach the browser.
Pairing token: JWT signed by the console, 5-minute life, single-use, carrying noggin_id and the brain’s address. The brain exchanges it for its own long-lived bearer token on first contact; thereafter the brain’s token is all the app uses. Signed in at talkto (milestone 10b), the app fetches one whenever it opens a noggin it has no token for, and once more if the brain refuses its token (silent renewal). A noggin with end-to-end on is never paired with a console token, which crosses Cloudflare: the app asks that device for a one-time E2- code from the noggin instead. Signing a device out in the console stops its pairing tokens at once; a device token it already holds is revoked only at the brain, because the console has no path to a brain.
Component 2b: Ops console (site administration)
ops.allmynoggins.com, a separate Next.js app sharing the console API and database but with its own OpsUser table. Sign-in rule: an OpsUser can only be created for an email address whose domain is allmynoggins.com or allmynoggins.net (allow-list enforced at creation and at every sign-in), with a passkey registered to that address; no OIDC or consumer SSO is accepted for ops. It administers providers, tiers, accounts and the hosted fleet; it has no code path to any brain’s API or folder.
Provider adapters (console/api/providers/): one interface, configuration-driven.
interface HostingProvider {
id: string; // "fly", "hetzner", "ionos"
create(spec: TierSpec): Promise<Instance>;
pause(id: string): Promise<void>;
resume(id: string): Promise<void>;
destroy(id: string): Promise<void>;
status(id: string): Promise<InstanceStatus>;
cost(id: string, period: Period): Promise<Money>;
}
A provider is enabled in Ops by entering credentials and regions; until one is enabled, personal consoles hide Host it for me. The first compute provider is deferred; Cloudflare is fixed as edge and DNS and recorded as a ServiceSetting.
Routes: /providers, /tiers, /accounts (search, suspend, delete with export), /fleet (every hosted brain: provider, region, status, heartbeat lag, cost; pause, restart, migrate), /templates (versioned starter voices, profiles, presets), /settings (edge, email provider, feature flags, legal text versions, maintenance notice), /audit, /health.
Tables added: OpsUser, Provider (id, enabled, credentials_ref, regions), Tier (name, provider_id, size, local_models, price_gbp, visible), ServiceSetting (key, value, version), Template (kind, slug, version, body), OpsAudit (ops_user_id, action, target, at).
Rules: destructive actions on a user’s noggin require two ops users (request and approve) and always offer export first; all ops actions are audited; credentials live in the worker’s secret store, never in Postgres.
Component 2c: Noggin designer
designmynoggin.allmynoggins.com: a stateless React app (shares packages/ui) that runs a structured interview and produces a personality archive. Two back ends, selected by whether a brain is paired:
- Brain-backed (default): the app opens a session on the paired brain with the built-in
designervoice (voices/_designer.md, shipped with the brain, privacy inherited from the owner’s brain profile) and streams drafts over the normal/v1/sessions/{id}/messagesendpoint; the result is written withPOST /v1/voices/import. - Platform-backed (trial):
POST /designer/sessionon the console API runs the same interview against a platform-held key for a small frontier model, capped at a token budget per session and per IP, logs only token counts, and returns the archive for download. No persistence.
Later: adapters/llm/designer_local wraps a fine-tuned open-weights model trained on published templates and consented interviews, and becomes the local backend for _designer. Archives carry made_with (brain, platform-designer, designer-local) for provenance. The same flow is embedded in the app under New voice → Design it with me.
Component 3: talkto.allmynoggins.com (PWA)
Decision: a Progressive Web App first; a native wrapper (Expo) only if hands-free talk or store presence proves necessary. The PWA is served at talkto.allmynoggins.com (one domain, no separate app domains); a person’s console opens it with the chosen noggin and pairing token in the URL fragment, or (milestone 10b) the owner signs in there with the console account and picks a noggin from the list; pairing codes and Scan pairing code remain the fallback. It installs to the home screen with the Talk mark as its icon.
Screens (match design/README.md section 07 for the chat and talk layouts)
| Screen | Content | State |
|---|---|---|
| Voice picker | Voice cards (name, purpose, privacy pill, host) from /v1/voices; New voice form; Design it with me | — |
| Chat | Message list with chat bubble pairs, Today separator, composer with text field and 48px accent mic button, rating on each reply, model and cost shown per reply; session adviser card on open | `voiceState: idle |
| Talk | Previous reply, live caption, level line, Speaker / Mic / End row, truthful processing caption | `micState: idle |
| Settings sheet | Theme (light, dark, system), noggin address, forget pairing, deletion controls with the ownership statement | theme kept on the brain |
Speech on the device: use the Web Speech API for speech-to-text and speechSynthesis for text-to-speech when the voice’s backend is device. On iOS Safari, recognition is processed by Apple, so the router treats device STT as not local. Decided: brain-side Whisper is offered as an option; local voices default to it wherever the host can run Whisper, and the app streams microphone audio to /v1/sessions/{id}/audio in that case. The processing caption states which path is in use.
Session adviser card: shown on opening a voice; three options from /v1/voices/{id}/advice with expected cost and latency; the pick sets the session bias and is remembered per voice. Availability: header caption and mic button reflect the brain state from the console or from a failed /v1/health probe, whichever is fresher.
Footprint and offline: the device holds only, for each noggin it is paired with, the pairing token (or end-to-end pairing secret) and the brain address (localStorage, cleared by Forget pairing for one noggin and by Sign out for all), the cached app shell and, signed in, the console’s HTTP-only session cookie, which no script reads (invariant 6 as amended 2026-10-18). No message history, memory, audio, drafts or per-voice preferences are stored on the device. Offline, the app opens and shows Noggin offline; it never answers on its own.
Streaming: POST /v1/sessions/{id}/messages returns SSE; audio for Talk arrives on a WebSocket at /v1/sessions/{id}/audio as Opus chunks, sentence by sentence.
Component 4: Marketing site
Astro on Cloudflare Pages, content in Markdown under site/content/. Pages: landing (hero as specified in design/README.md section 07), Voices (voice cards generated from the founder’s real voice files, redacted), Build log (rendered from docs/BUILDLOG.md), Docs (rendered from docs/), privacy page. Interest list posts to Buttondown; no analytics beyond Cloudflare’s server-side counts, stated on the page. The Owner sign-in button links to the console.
Design system integration
The brand system v0.1 is final for colour, type, spacing, radius and copy. Claude Code recreates it; it does not redesign it.
Tokens
design/tokens.cssis imported once, inpackages/ui, and is the only place colour, font, space and radius values appear. Component code referencesvar(--color-accent), never a hex.- Dark mode is
[data-theme="dark"]on<html>, set from thethemestate (systemfollowsprefers-color-scheme). design/tokens.jsonis kept beside it for other tooling; it must stay in sync withtokens.css, enforced by a test.
Components in packages/ui (specs in design/README.md sections 05 and 08): Button (primary, secondary), TextInput with label and helper, Card, PrivacyPill, TopNav, VoiceCard, ChatBubble, MicButton (four states), OutputSegmented, LevelLine, ThinkingDots, Wordmark and Mark (stack, noggin, talk; colour, mono, reversed). Icons from Lucide at 24px, stroke 2. Every component has light and dark stories.
Brand architecture in code: the stack mark appears only in the marketing site nav; the noggin mark in docs, README, CLI and favicon; the talk mark as the PWA icon and in the app header. Never all three as equals. The app wordmark is talkto.allmynoggins; the name Talk2UrNoggin is retired.
Privacy level mapping. The design system names the levels in plain English; the brain uses machine keys.
| Design label | Brain key | Behaviour |
|---|---|---|
| Local only | local | Nothing leaves the host; cloud adapters refused |
| Asks before sharing | asks | Redaction pass runs, then the owner confirms once per session before a cloud call |
| Uses the web | web | Cloud model and speech adapters allowed, full prompt |
Decided: asks is once per session; the first cloud call in a session prompts the owner, later calls in that session do not; a Consent row is keyed by session.
Truthful copy rule: the talk-screen caption (“Audio is processed on pi-kitchen…”) is generated from the actual backends chosen for that turn, never hard-coded.
Motion: only the three keyframes in tokens.css; all stop under prefers-reduced-motion.
API contracts
Brain API (https://<brain-address>/v1, bearer token, JSON, SSE for streams)
| Method and path | Purpose |
|---|---|
POST /pair | Exchange a console pairing token or a local one-time code for a brain bearer token |
GET /pair, POST /pair/e2e/start, POST /pair/e2e/finish | End-to-end pairing (05b): the noggin’s e2e setting; SPAKE2 over the one-time code, which is never sent |
POST /e2e/channel, POST /e2e/call, WS /e2e/talk, GET/PUT /e2e | End-to-end mode: open a sealed channel; one sealed inner request (any route above) with its reply as sealed frames; sealed talk mode; the owner’s switch |
GET /health | Host capability: RAM, GPU, engines, models, speech backends, version |
GET /voices, POST /voices, PATCH /voices/{id}, DELETE /voices/{id} | Voice registry; POST takes the short-form fields and writes the Markdown file; delete removes the memory scope too |
GET /voices/{id}/advice | Session adviser options |
POST /voices/{id}/export, POST /voices/import | Personality archives |
GET /voices/{id}/prompts, POST /voices/{id}/prompts | Prompt library |
POST /sessions (bias), GET /sessions?voice=, DELETE /sessions/{id} | Conversations |
GET /sessions/{id}/messages | History, paged |
POST /sessions/{id}/messages | Send text; SSE events token, run (model, provider, local, tokens, cost, redacted), done |
DELETE /messages/{id} | Remove one message |
WS /sessions/{id}/audio | Talk mode: client sends audio frames or text; server sends caption, token, audio (Opus), run |
POST /messages/{id}/feedback | rating, note, promote_to_memory |
GET /memory?scope=, DELETE /memory/{id} | Inspect and remove memory |
GET /costs?period= | Spend by voice, by provider, by kind (model, speech, tool) |
POST /consent | Records an asks confirmation for a session |
POST /export, POST /import | Whole-noggin archive |
POST /models/pull, GET /models/pulls | Install model: the host’s Ollama pulls a catalogue model on the owner’s tap; progress |
DELETE /runs, POST /wipe | Clear the audit log; wipe the entire folder (passphrase required) |
Console API (https://api.allmynoggins.com/v1, session cookie)
| Method and path | Purpose |
|---|---|
POST /signup, POST /login (WebAuthn or OIDC), POST /logout | Accounts; sign out ends this browser’s session |
GET /account/sessions, DELETE /account/sessions/{id} | Signed-in devices: list, sign one out |
POST /subdomain | Claim or check a subdomain |
GET /noggins, POST /noggins, PATCH /noggins/{id}, DELETE /noggins/{id} | Pointers and hosted lifecycle (hosted: true triggers creation when a provider is enabled) |
POST /noggins/{id}/pairing-token | Short-lived JWT for the app to pair (also called by talkto, signed in) |
POST /noggins/{id}/install-package | Install package for a target |
POST /noggins/register | Called by a brain with its address and heartbeat |
POST /designer/session | Platform-backed designer trial |
GET /billing | Hosted spend and invoices |
Both APIs are described in OpenAPI 3.1 files committed to the repo; clients are generated from them.
Data and storage
The noggin/ folder (brain; all open formats)
noggin/
config.yaml exposure, engines, keys reference, registry on/off, profile
secrets.enc model and speech provider keys only, encrypted with the passphrase-derived key
voices/<slug>.md one voice: YAML front-matter plus system prompt
voices/<slug>/prompts/*.md
voices/<slug>/media/ produced files (v1.5)
memory.db sessions, messages, memory + embeddings (sqlite-vec), prompts, feedback, consent, learned weights (encrypted at rest)
runs.db ModelRun, SpeechRun, ToolRun audit, append-only
Voice front-matter schema
name: Career adviser
privacy: asks # local | asks | web
model:
default: claude-sonnet
fallback: llama3.1-8b-local
speech:
stt: device # device | whisper-local | elevenlabs | deepgram
tts: kokoro-local # device | kokoro-local | chatterbox-local | elevenlabs | openai | cartesia
voice_id: null
memory_scope: career
reads: [~/notes/work] # shown on the voice card as "reads: ..."
tools:
read: [web, calendar]
produce: []
draft: []
spend_cap_gbp_month: 20
schedule:
- cron: "0 7 * * *"
prompt: "Check fares for the Lisbon trip"
Brain tables: Session, Message, Memory (scope, kind, content, embedding), Prompt, Feedback, LearnedWeight (voice, class, model, score), ModelRun, SpeechRun (backend, seconds, cost, local), ToolRun, Consent (session, granted_at).
Console tables (Postgres): Account (id, email, created_at, accepted_terms_at), AuthMethod, Subdomain (slug, account_id), Noggin (id, account_id, name, address, hosted, tier, status, last_seen, last_export_at, accepted_at), HostedInstance (noggin_id, provider_id, instance_id, volume_id, region), Invoice, plus the Ops tables above. No message content, no voice content, no keys. A schema test enforces this list.
Outbound actions and tools
Invariant: the brain never acts on the world. There is no Act tier and no code path that submits, posts, pays, sends or cancels. Three capability tiers are granted per voice under tools: and enforced by a tools/ module behind the router’s privacy gate. v0.1 ships Read only.
| Tier | Module | v0.1 | Interface |
|---|---|---|---|
| Read | tools/read/ | yes | search(query), fetch(url), calendar.read(), mail.read(); local voices refused; asks voices show the query first |
| Produce | adapters/media/ | v1.5 | image(prompt), video(prompt, assets), audio(prompt); files to voices/<slug>/media/; MediaRun row with model, prompt, cost |
| Draft | tools/draft/ | v1.5 | Returns a Draft (kind, payload, preview, handoff) that the app renders as a card with one button; handoff is a share intent, deep link or copy-to-clipboard into the platform’s own app, never an API call made by the brain |
Requirements
- No posting, payment or messaging credentials are stored in the brain;
secrets.encholds model and speech provider keys only, enforced by a key-type allow-list. - A
Draftcreated in the same turn as afetchormail.readcarriesafter_external_content: trueand the app shows a warning on the card. - Scheduled jobs may Read, Produce and Draft; a job that results in drafts shows them as cards on next open, badge counted.
- Spend caps are checked by the router before Produce; exceeding one returns a plain refusal with the figure.
- Every tool call writes a
ToolRuntoruns.dbbefore execution. - A test suite asserts that no module imports an HTTP client except
adapters/andtools/read/, and thattools/draft/has no network access at all.
Security requirements
- Egress allow-list in the brain. Only
adapters/andtools/read/may open outbound connections, to the hosts configured, plus the console registry endpoint. Enforced in code and by the container’s network policy where available. - Audit before send. A
ModelRun,SpeechRunorToolRunrow is committed before any outbound request; a failure to write aborts the call. - Privacy level is a hard gate. The router cannot select a cloud adapter for a
localvoice under any fallback; forasks, no cloud call without aConsentrow for the session. - Encryption at rest.
memory.db,secrets.encand the export archive use a key derived from the owner’s passphrase (Argon2id); the brain holds the key in memory only while running. - Transport. TLS everywhere. Default exposure is
tunnel: a per-noggin Cloudflare Tunnel, no inbound port on the host, HTTPS terminated at Cloudflare’s edge (accepted; see Trust in transit). End-to-end encryption between app and brain is an owner option from milestone 05b. The brain refuses to start inpublicmode without its own certificate. - Tokens. Console pairing JWTs expire in 5 minutes and are single-use; brain bearer tokens are revocable from the brain and listed in the app’s settings.
- Console minimalism. The console database never stores message, memory, voice or key content; a schema test enforces the table list.
- Hosted isolation. One machine and one volume per noggin; no shared database; the control worker holds provider credentials, the browser never does.
- Dependencies. Pinned versions; images rebuilt and scanned weekly.
- Account deletion removes all pointers and hosted machines within 24 hours, after an export is offered.
Deletion and liability requirements
- Deletion endpoints on the brain:
DELETE /v1/messages/{id},DELETE /v1/sessions/{id},DELETE /v1/memory/{id},DELETE /v1/voices/{id}(with its memory scope),DELETE /v1/runs(audit log),POST /v1/wipe(entire folder, passphrase required). Each removes rows and embeddings and runsVACUUM; a test asserts no residue. - The app’s settings sheet and the console’s Manage screen expose these with a confirmation, and show the ownership statement beside them: “This noggin is yours. You can delete anything in it at any time. You are personally responsible for everything it stores or produces.”
- The same statement is accepted once at account creation and once at first brain creation (
Account.accepted_terms_at,Noggin.accepted_at), and is written into the export archive’s and every install package’sREADME.md. - Hosted terms include suspension or deletion on a valid legal notice; the worker supports
suspendfor that case. Solicitor review before the hosted tier opens.
Deployment
| Component | Where | How | Approx. monthly cost |
|---|---|---|---|
| Marketing site | Cloudflare Pages | Git push builds Astro; custom domain, TLS automatic | £0 |
| talkto, designer | Cloudflare Pages with wildcard *.allmynoggins.com | Vite builds; wildcard certificate from Cloudflare Free | £0 |
| Console, ops and the registry | Cloudflare Workers (Next via OpenNext) with D1, Durable Objects and Cron Triggers; my, api, get and ops hostnames | Milestone 13; until then the Node build runs only locally | $5 (Workers Paid) |
| Platform database | Cloudflare D1 | Pointer rows only; browsable in the dashboard; nightly export with wrangler d1 export | £0 |
Noggin host gpu-1 (founder’s and demo noggins, later hosted ones) | GigaGPU dedicated bare metal (UK), RTX 5060 Ti 16 GB, Ryzen 3700X, 32 GB RAM, 1 TB NVMe + 1 TB SATA, Ubuntu 24.04, ordered 2026-10-23; plus the laptop | Docker, the node agent (milestone 14), native Ollama shared by every noggin on the box, shared Whisper and Kokoro services (see Shared speech engines on a GPU host), a Cloudflare Tunnel sidecar per noggin | ~£130 |
| Hosted brains | On noggin hosts through the noggin host adapter, behind the hosting flag (milestone 14) | — | — |
| Bigger GPU | Only if a measured queue appears: an RTX 3090 24 GB (£199) at the same provider; vLLM only then | — | — |
Shared speech engines on a GPU host (planned 2026-10-23, built 2026-10-24 in milestone 12)
On a machine that runs several noggins, the host’s native Ollama is already one engine shared by every container (OLLAMA_URL), and it is the only engine the router treats as local. Whisper and Kokoro are different: whisper_local runs the whisper.cpp binary built into the image and kokoro_local imports kokoro_onnx in-process, so each container carries its own copy of each model and has no GPU unless it is handed the card. On gpu-1 they become two shared services instead. Nothing else moves.
flowchart TB
subgraph platform["allmynoggins platform · pointers, accounts, templates, settings · never a message, memory, voice file or key"]
direction LR
site["allmynoggins.com<br/>site"]
console["console + API<br/><slug>.allmynoggins.com"]
pg[("Postgres<br/>pointers only")]
talkto["talkto<br/>static PWA shell"]
designer["designer<br/>stateless"]
ops["ops<br/>staff passkeys"]
end
phone["talkto on the owner's phone<br/>holds only: session cookie, brain address, device token"]
subgraph gpu1["gpu-1 · GigaGPU bare metal · Ubuntu 24.04 · Docker"]
direction TB
subgraph n1["noggin container · <name>"]
t1["cloudflared sidecar<br/><name>.allmynoggins.net"]
b1["brain<br/>api → router → privacy gate<br/>llm: ollama · anthropic · openai_compat<br/>speech: whisper_http · kokoro_http ①<br/>host.py probes Ollama and the speech services ②<br/>audit → runs.db before every call"]
end
subgraph n2["noggin container · demo <name>"]
t2["cloudflared sidecar"]
b2["brain · same image<br/>OLLAMA_URL · WHISPER_URL · KOKORO_URL from the compose file ③"]
end
ollama["Ollama · native on the host · 127.0.0.1:11434<br/>already shared · already local · swaps models on demand · unchanged"]
subgraph engines["Docker network engines · private · no tunnel, no inbound port ③"]
whisper["Whisper service<br/>Speaches or faster-whisper · GPU<br/>:8001 /v1/audio/transcriptions"]
kokoro["Kokoro service<br/>kokoro-fastapi · GPU<br/>:8002 /v1/audio/speech"]
end
gpu["RTX 5060 Ti 16 GB: 8B general + 3–4B small (Ollama) · Whisper turbo · Kokoro · KV cache"]
end
subgraph laptop["noggin on a laptop or Pi · unchanged"]
bl["brain<br/>whisper_local · kokoro_local in-process<br/>Ollama native on that machine"]
end
cloud["cloud model and speech providers<br/>reached only from a brain's adapters/<br/>only where the voice's level allows · logged first"]
phone -. "sign in · list noggins · pairing token" .-> console
phone -- "tunnel" --> t1
phone -- "tunnel" --> bl
b1 -. "heartbeat 60 s" .-> console
b1 --> ollama
b2 --> ollama
b1 == "new" ==> whisper
b1 == "new" ==> kokoro
b2 == "new" ==> whisper
b2 == "new" ==> kokoro
b1 -. "web and asks voices only" .-> cloud
bl -. "web and asks voices only" .-> cloud
The three change points
| Where | Today | Change | |
|---|---|---|---|
| ① | brain/noggin/adapters/speech/, brain/noggin/router/speech.py (STT_WHERE, TTS_WHERE) | whisper_local is a whisper.cpp subprocess; kokoro_local is in-process; the maps give each backend a locality of noggin, device or cloud, which is what the privacy gate uses for speech | Two new adapters, whisper_http and kokoro_http, speaking the OpenAI-style audio endpoints the services expose, base URLs from config. Both map to noggin (same host), so every privacy level may use them and the talk caption stays true. The in-process adapters remain for laptops and Pis. Both calls open from adapters/, so invariant 5 and its test are unchanged |
| ② | brain/noggin/host.py | Capability probes Ollama only and publishes engines/models at /v1/health; the router, the console’s Online state and Install model read that snapshot | Probe the configured speech services too and publish them under speech_backends, so plan_speech sees the HTTP backends as ready and a noggin whose services are down falls back to device speech and says so. Ollama probing is untouched |
| ③ | templates/install/docker-compose.yml, console/api/installer/ | Every service uses network_mode: host; Ollama is native; the brain gets OLLAMA_URL. Targets are laptop, vps-linux, cloud-vm | A gpu-server host preset: a compose file that creates the private engines network, runs the Whisper and Kokoro services once with the GPU, and starts each noggin as a client with WHISPER_URL and KOKORO_URL beside OLLAMA_URL. Tunnel sidecar, port offsets and the single-use pairing token stay as they are. The GPU-server image may omit whisper.cpp and the ONNX model |
Unchanged by this: the privacy gate and its tests (Ollama remains the only local LLM engine), audit before send, the egress allow-list, one tunnel per noggin, the mouthpiece, console, ops, designer and site, voice files and the noggin folder, export and import (a noggin moved off gpu-1 falls back to the in-process adapters with no edit to its folder).
Not adopted: vLLM, and with it engine locality in config.py (the ModelEntry validator “only ollama models are local”), keyless readiness in brain.py, and an amortised hardware cost per reply. Ollama with OLLAMA_NUM_PARALLEL serves a handful of concurrent visitors on an 8B model; vLLM pays off only at tens of simultaneous chats on one card and cannot swap models on demand, which Install model relies on. Revisit when a queue is measured.
As built (milestone 12): the gpu-server install target writes templates/install/engines/docker-compose.yml to /srv/engines/ with one .env (ENGINES_DEVICE=cuda|cpu, WHISPER_MODEL) that the engines project and every noggin on the machine read; Compose profiles cpu and cuda pick the images (Speaches 0.8.3-cpu|cuda, Kokoro-FastAPI v0.9.0 CPU or v0.9.0-cu128), and the card is reserved only under cuda. On a CPU the Whisper model is Systran/faster-whisper-small.en (about 3 s for a short utterance on a laptop, against 13–21 s for turbo); on the card deepdml/faster-whisper-large-v3-turbo-ct2. A gpu-server noggin runs on Docker’s networks rather than the host’s (it must join engines), so it reaches Ollama at host.docker.internal: Ollama on such a host listens on 0.0.0.0:11434 with a ufw rule admitting only Docker’s private range 172.16.0.0/12. One image everywhere (owner, 2026-10-24): the in-process adapters stay in the image and NOGGIN_SPEECH_IN_PROCESS=off, set by the compose file and never by config.yaml, turns them off, so a folder moved to a laptop gets them back. The brain probes both services with the host snapshot (5-second TTL), lists them under speech_backends at /v1/health and, by name and state only, in the heartbeat; a talk rechecks before each turn and, when a service fails mid-reply, marks it down and hands the sentence to the next choice (the device’s voice), with a new caption. A voice asking for whisper-local or kokoro-local gets the shared service first.
Rules for the shared services: they listen only on the engines network, never behind a tunnel and never on a host port; every transcription and speech call is still written to runs.db as a SpeechRun before it starts; a noggin off this host never calls these engines (a hop off its own host is neither local nor cloud; a fourth locality is a [decide]).
Demo video uploads (proposed, [decide]): uploads land on the SATA disk at /mnt/spool, size and duration capped, rate-limited per visitor; the audio track is extracted by ffmpeg in a container with no network and no access to any noggin folder; the video is deleted as soon as the audio is out and the audio as soon as the transcript is written; Whisper runs one job at a time with the visitor shown their place in the queue. This is a Read-tier action with no upload endpoint on the brain today, so it is a milestone of its own. [decide] whether it shares the Whisper queue or gets a lower-priority worker.
DNS and edge (Cloudflare Free plan): zone allmynoggins.com with nameservers moved from Porkbun; records @, talkto, ops, designmynoggin, api and the wildcard *, all proxied; Universal SSL covers them. A second zone, allmynoggins.net, is the noggins’ namespace: each self-run brain is reached through its own Cloudflare Tunnel at <name>.allmynoggins.net (proxied CNAME to the tunnel, covered by that zone’s free Universal SSL; two-level names under .com would need a paid certificate). Email Routing provides admin@ and hello@ forwarding; transactional sending through Workers Paid or Resend when the console needs it.
Owner experience for self-run noggins (decided 2026-10-17). Running a noggin on your own machine must need no knowledge of Docker, Ollama, Cloudflare, DNS or tokens. The rules:
- One action to install. The console’s Run it myself gives one download per platform; the owner runs one command (or double-clicks on Windows and macOS). It installs or reuses Docker and Ollama, picks and pulls models for the machine’s RAM with visible progress, starts the noggin and its tunnel sidecar, and sets everything to start again after a reboot or login. Ollama stays native on the host (GPU and Apple-silicon acceleration); everything of the noggin’s own is in containers.
- The owner never opens Cloudflare. Each noggin’s tunnel, its route and its
<name>.allmynoggins.netrecord are created by the console through the Cloudflare API in the platform’s account, and the tunnel token arrives inside the package. Owners need no Cloudflare account. - Pairing ends the install. The installer finishes by showing a QR code and a link. On a phone the owner installs the app from talkto.allmynoggins.com first, then scans the code with the app’s Scan pairing code (camera) or types the name and code. Install-then-pair is the documented order because iOS keeps an installed web app’s storage apart from the browser.
- Re-running repairs. Running the installer again on an installed machine changes nothing if all is well, and puts back whatever is missing (a stopped container, a missing model, a lost start-at-login entry).
noggin doctorchecks Docker, Ollama, models, the tunnel, disk space, the clock and folder permissions, and prints each problem with the exact fix. - Updates are one action.
noggin updatetakes an encrypted backup first, pulls the newest published image, restarts with the same folder, and rolls back if the new version does not pass its health check. When the console heartbeat is on, the app shows Update available; the brain itself never phones out for this (egress rule). - Backups happen by themselves. An encrypted export runs on a schedule (
backup:in config.yaml, daily and keep 7 by default) to a folder the owner chooses. - Missing models are one tap. When a voice is unavailable because a model is missing, the app offers Install llama3.1:8b (4.9 GB); on the owner’s tap the brain asks the host’s Ollama to pull it, with progress.
- Leaving is clean. The uninstaller offers an export first, then removes the containers, the start-at-login entries and, if asked, the folder; it never removes Docker or Ollama the owner had before.
- Moving is export, start, re-pair. Moving to another machine is an encrypted export, the installer on the new machine with Import, and the same address keeps working because the tunnel token travels in the folder.
Plain-English deployment of a self-run brain: download the install package for the target from the console, run install.sh (or paste cloud-init.yaml into the provider), wait for the console to show online, open the app.
Build order for Claude Code
Each milestone is one Claude Code session or a short series; each ends with something runnable. Prompts are under prompts/.
| # | Milestone | Done when |
|---|---|---|
| 0 | Monorepo scaffold, packages/ui with tokens and components, Storybook in light and dark | Stories match design/ section 05 pixel for pixel |
| 1 | Brain core: config, voice loader, SQLite memory, Ollama adapter, /v1/health, /v1/voices, /v1/sessions, streaming messages | Chat with a local voice from curl |
| 2 | Router and audit: rules.yaml, Anthropic and OpenAI-compatible adapters, redaction, asks consent, runs.db, /v1/costs, adviser, scheduler | A local voice refuses the cloud; costs appear per reply |
| 3 | talkto.allmynoggins.com PWA: voice picker, chat, settings, pairing, installable | From a phone’s browser, talkto.allmynoggins.com pairs with the laptop brain at its .allmynoggins.net address, chats, and installs to the home screen |
| 4 | Speech: device STT and TTS; Kokoro and Whisper adapters; audio WebSocket; talk screen with all mic states | Talk mode works end to end |
| 5 | Export and import, personality archives, deletion, encryption, multi-arch image, binary | Move a noggin from the laptop to the server and re-pair |
| 5b | End-to-end encryption option: pairing by PAKE over the one-time code, encrypted request bodies, streams and audio, safety number | A recording proxy in front of the brain sees no message or reply text |
| 6 | Console: accounts, passkeys and OIDC, subdomain claim, pointers, pairing tokens, Open, availability | <slug>.allmynoggins.com opens the founder’s server noggin |
| 6b | Install package generator for laptop, VPS and cloud VM; console-provisioned tunnels; QR pairing | Each target comes online from a downloaded package |
| 7 | Ops console, provider adapter interface, templates, settings, audit | Domain-locked ops sign-in; hosting hidden until a provider is enabled |
| 8 | Marketing site with real voice cards and build log; designer | allmynoggins.com live with the interest list |
Milestones 0 to 5 need nothing from platform infrastructure, so the sovereignty story can be demonstrated before any account system exists.
Decision log
See docs/decisions.md (maintained in the repository).
Open questions
-
asksconsent: once per session - iOS speech: brain-side Whisper offered;
localvoices default to it where the host can run it - Subdomain scope: one subdomain per account in v0.1, or allow a vanity alias per noggin
- Hosted tier at launch: none until a compute provider is enabled in Ops; Cloudflare fixed as edge and DNS
- Scheduler: timed jobs in v0.1
- Vector index: sqlite-vec inside
memory.db - Console stack: Next.js on Cloudflare Workers with D1 (decided 2026-10-24; milestone 13)
- Billing provider: Stripe or a merchant of record
- The opt-in relay for noggins that cannot run a tunnel: whether and when
- Founder’s real voices: which three ship on the site, and what is redacted
- Adviser threshold: 20 ratings per class to take over, Fully mature at 50 (decided 2026-10-24)
- Shared learning model: minimum batches before a total is published (proposed 10)
- Founder’s own host: Docker on a laptop first; always-on is
gpu-1(GigaGPU bare metal, ordered 2026-10-23) in place of the Hetzner auction server - A fourth engine locality for a noggin on another machine the owner owns (laptop or Pi calling
gpu-1’s engines); out of scope until asked for - Demo video transcription: shares the Whisper queue, or a separate lower-priority worker
- GPU-server image: one image; the in-process speech adapters stay and the gpu-server compose file turns them off (decided 2026-10-24)