# Maskinrommet — teknisk tjenestelaget > Én Rust-tjeneste med et fast grensesnitt. Alle tekniske tjenester beveger > seg gjennom dette laget. Fang, prosesser, lever. ## Observasjoner I dag er tekniske tjenester spredt: - **Worker** (Rust) — kjører bakgrunnsjobber - **Jobbkø** (PG) — koordinerer arbeid - **AI Gateway** (LiteLLM) — ruter AI-kall - **Whisper** — transkripsjon - **LiveKit** — lyd/video-strømmer Hver har sitt eget grensesnitt. Frontend og primitiv-laget må vite hva som finnes under panseret. Det er ingen felles abstraksjon, ingen felles logging, ingen felles kapasitetsstyring. ## Tesen Alt som krever tunge ressurser eller eksterne tjenester går gjennom **ett lag** med **ett grensesnitt**. Ikke fordi det er elegant — fordi det gir et fast punkt som er enkelt å fange, modifisere og forbedre. Maskinrommet gjør tre ting: ### 1. Fang (input-absorpsjon) Ta imot råmateriale i alle modaliteter: - Tekst (melding, URL, dokument) - Lyd (voice memo, live stream, filopplasting) - Bilde (foto, skjermbilde, tegning) - Video (stream, opptak) - Strukturert data (JSON, metadata, edges) ### 2. Prosesser (transformasjon) Analyser, transformer, berik og systematiser: - **STT** — lyd → tekst (Whisper) - **TTS** — tekst → lyd (ElevenLabs / lokal modell) - **AI-analyse** — oppsummering, klassifisering, sentimentanalyse, faktasjekk, edge-forslag - **Beriking** — URL → metadata, bilde → beskrivelse, lyd → segmenter - **Søk** — fulltekst, semantisk (pgvector), graftraversering - **Mediaprosessering** — transcode, thumbnail, waveform ### 3. Lever (output-distribusjon) Lever resultat i riktig modalitet til riktig mottaker: - Tekst (melding, notifikasjon, digest) - Lyd (TTS-opplesning, lydstream) - Video/bilde (stream, thumbnail, snapshot) - Strukturert data (noder, edges, metadata tilbake i grafen) - Push (webhook, SSE, SpacetimeDB-reducer) ## Edge-drevet ressursorkestrering Nøkkelinnsikten: **maskinrommet leser edges for å vite hva det skal gjøre.** Noden selv er alltid enkel. Det er edgene som bestemmer hvilke ressurser som spinnes opp. ### Security by default Input uten mottaker-edge er automatisk privat. Du trenger ikke "velge privat" — det er utgangspunktet. Ingen ser det. Ingen ressurser kobles inn utover det grunnleggende (fang + transkriber). Privat er ikke en innstilling, det er fravær av deling. ### Ressurser er proporsjonale med edges Samme nodetype, vilt forskjellig ressursbruk: ``` Dagboknotat (privat voice memo): node → fang lyd → transkriber (Whisper) → lagre Ressurser: minimal Samtale med Trond: node + mottaker-edge(Trond) → fang lyd → transkriber → lever tekst/lyd til Trond Ressurser: STT + levering til én Redaksjonsmøte (5 deltakere): node + mottaker-edges(5) + rolle-edges → fang lyd fra alle → transkriber → lever til alle → AI-referent Ressurser: STT + levering til 5 + LLM Livesending (1000 lyttere): node + mottaker-edges(∞) + stream-edge + publiserings-edge → fang lyd → transkriber → stream via LiveKit → distribuer → generer segmenter → kjør live AI → publiser Ressurser: STT + LiveKit + LLM + mediaprosessering ``` Maskinrommet gjør ikke mer enn det edges krever. Ingen overhead for enkle ting. Noden vet ingenting om LiveKit — den har bare edges som sier "stream til disse mottakerne", og maskinrommet bestemmer at det betyr LiveKit. ### Naturlig eskalering Du starter en privat voice-note. Bestemmer deg for å dele den med Trond → legg til mottaker-edge, maskinrommet begynner å levere. Trond foreslår at dere tar det som et møte → legg til flere deltaker-edges, maskinrommet kobler inn sanntidsstrømming. Møtet blir en innspilling → legg til publiserings-edge, maskinrommet aktiverer produksjonspipeline. Hvert steg er bare å legge til edges. Maskinrommet reagerer og kobler inn flere ressurser etter hvert. Ingen migrering, ingen modebytte. ## Grensesnittet Maskinrommet eksponerer et konsistent API — sannsynligvis en Rust trait eller et sett traits: ``` fang(input: RåInput) → NodeId prosesser(node: NodeId, operasjon: Operasjon) → Resultat lever(node: NodeId, mottaker: Mottaker, format: Format) → Status ``` Men i praksis er mye av dette *reaktivt*: maskinrommet observerer edge-endringer og handler automatisk. Legger noen til en mottaker-edge → maskinrommet begynner å levere. Legger noen til en stream-edge → maskinrommet kobler inn LiveKit. Primitivene trenger ikke eksplisitt kalle `lever()` — de manipulerer edges, og maskinrommet reagerer. ## Hva dette gir ### Isolasjon Bytt Whisper med noe annet? Endre maskinrommet. Frontend vet ingenting. Legg til bildegenerering? Ny operasjon i maskinrommet. Primitivene kaller den uten å vite hva som skjer under. ### Observerbarhet Alt går gjennom ett punkt. Logging, metrikker, kostnadsrapportering, feilhåndtering — alt på ett sted. "Hva bruker vi AI-ressurser på?" har ett svar. ### Kapasitetsstyring Prioritering, kø, rate limiting, fallback mellom leverandører — alt håndtert av maskinrommet. En podcastinnspilling som trenger live transkripsjon kan prioriteres over en bakgrunns-oppsummering. ### Fast utviklingspunkt To team (eller to hatter) med klart grensesnitt: - **Over maskinrommet:** primitiver, noder, edges, UI, brukeropplevelse - **I maskinrommet:** ytelse, integrasjoner, kapasitet, kostnad Du kan perfeksjonere det ene uten å røre det andre. ## Content-Addressable Storage og intelligent pruning Maskinrommet forvalter også lagring. Ikke alt kan lagres for evig — men ikke alt trenger det heller. Signalene for hva som er viktig finnes allerede i grafen. ### CAS som lagringsprimitiv All binærdata (lyd, bilde, video) lagres i et content-addressable store. Fordeler: - **Deduplisering gratis** — samme fil delt i tre kontekster = én kopi - **Separasjon** — "innholdet eksisterer" er adskilt fra "innholdet er tilgjengelig." Noden peker på en hash, CAS har filen (eller ikke). - **Enkel opprydning** — slett hashen fra CAS, alle noder som pekte dit mister binærdataen men beholder metadata og transkripsjon. ### Lagringsregler per modalitet | Modalitet | Default levetid | Begrunnelse | |-----------|----------------|-------------| | Tekst | Evig | Billig, er essensen av innholdet | | Transkripsjon | Evig | Tekstlig representasjon av lyd/video — tar vare på meningen | | Lyd | 30 dager | Mellomkostnad, transkripsjon bevarer innholdet | | Bilde | 30 dager | Mellomkostnad, beskrivelse/metadata bevarer kontekst | | Video | 14 dager | Dyrest, transkripsjon + thumbnail bevarer det meste | ### Signaler som forlenger levetid Default-TTL er bare utgangspunktet. Maskinrommet justerer basert på: - **Edges.** En lydfil med edge til episoderegisteret = publisert podcast, beholdes. En privat voice-memo uten edges = 30-dagers TTL. - **Aksesslog.** Hvis noen har spilt av lydfilen i løpet av TTL-perioden, forlenges den. Ingen aksess = ingen verdi i å beholde binærdataen. - **Transkripsjonsstatus.** Lyd som er transkribert har "overlevert sin essens" til tekst. Lyd som *ikke* er transkribert (f.eks. musikk, lydeffekter) kan trenge lengre TTL. - **Edge-type.** Edge til publisert innhold = behold. Edge til arkivert møte = transkripsjon holder. Edge til ingenting = teksten lever videre, binærdataen kan dø. ### Eksempler ``` Privat voice-memo, aldri delt: → Lyd transkriberes → tekst lagres evig → Lydfil: 30 dager, ingen aksess, ingen edges → slettes → Noden lever videre med teksten Podcastepisode: → Lyd har edge til episoderegister + publiserings-edge → Aksesseres regelmessig via podcastarkivet → Lydfil: beholdes så lenge edges og aksess tilsier det Rutinemøte for et år siden: → Video (6 kanaler): ingen har sett den på 6 måneder → slettes → Lyd: ingen har spilt av → slettes → Transkripsjon: tekst, lagres evig. Søkbar, refererbar. → Noden lever med full kontekst minus binærdata Viktig styremøte: → Video aksesseres av styremedlemmer → forlenges → Workspace-innstilling: "behold video i 1 år" → overrider default ``` ### Generert innhold er en cache TTS, thumbnails, AI-oppsummeringer, waveforms — alt som kan regenereres fra kildedata er i praksis en cache. Det lagres i CAS med samme TTL- mekanisme som alt annet: - Peter ber om lyd-versjon av en tekstmelding → TTS genereres, lagres - Ingen spiller den av på 30 dager → filen slettes fra CAS - Peter (eller noen andre) ber om lyd igjen → regenereres on-demand - Teksten er der alltid. Binærdataen er flyktig. Maskinrommet trenger ikke skille mellom "original lyd" (voice memo) og "generert lyd" (TTS) i pruning-logikken. Begge er binærdata i CAS med en TTL som forlenges ved aksess. Forskjellen er bare at generert innhold alltid kan gjenskapes fra kilden — så det er tryggere å prune. ### Workspace-styrt aggressivitet Hvert workspace kan justere sin pruning-profil: - **Konservativt** — behold alt lenge (f.eks. arkiv-workspace) - **Aggressivt** — tekst bevares, binærdata prunes raskt (f.eks. daglig drift-workspace med mye rutineinnhold) - **Tilpasset** — egne regler per modalitet og edge-type ### Brukerens erfaringsbaserte meny Over tid bruker du noen edges oftere enn andre, noen noder oftere enn andre. Maskinrommet observerer dette og tilbyr en erfaringsbasert meny: dine mest brukte koblinger, dine vanligste input-mønstre, dine foretrukne modaliteter. Ikke som en rigid konfigurasjon — som en adaptiv overflate du kan aktivere og deaktivere fortløpende. Dette er ikke maskinlæring eller kompleks AI — det er frekvenstelling på edges og aksesslog. Enkelt å implementere, intuitivt for brukeren. ## Pragmatisk vei dit Ikke bygg dette fra scratch. Formaliser det som allerede finnes: 1. **Worker + jobbkø er allerede kjernen.** De trenger et konsistent API, ikke en omskriving. 2. **AI Gateway (LiteLLM) absorberes** — i stedet for en separat proxy, blir LLM-kall en operasjon i maskinrommet som alt annet. 3. **Whisper, TTS, mediaprosessering** — allerede planlagt som worker-jobber. Gi dem samme grensesnitt. 4. **LiveKit** — den mest spesielle tjenesten (sanntidsstrømmer). Kan starte som en separat integrasjon og formaliseres inn over tid. Rekkefølge: definer traits → migrer eksisterende worker-jobber inn → legg til nye tjenester etter hvert. Fast punkt fra dag én, full dekning over tid. ## Compute-separasjon Maskinrommet orkestrerer — men tunge jobber trenger ikke kjøre på samme maskin. Hetzner CPX42 (8 vCPU, 16 GB RAM) skal håndtere state (PG, SpacetimeDB) og sanntid (Caddy, LiveKit, SvelteKit). Whisper (CPU-intensiv, spesielt large-v3) og lokal LLM (kildevern-modus) vil konkurrere om ressurser under live innspilling. Maskinrommets abstraksjon gjør dette løsbart: - **Nå:** Alt på én VPS. Jobbkøen prioriterer sanntid over batch. Whisper kjøres med lavere concurrency under live-sesjoner. - **Senere:** Trekk ut tunge workers til en separat node (billig ARM/Ampere-instans) som poller jobbkøen over internt nettverk. Maskinrommet ruter transparent — primitivene merker ingenting. - **Kildevern-modus:** Lokal LLM (Llama/Gemma) krever GPU eller dedikert compute. Urealistisk på delt VPS. Egen node for dette. Poenget: maskinrommet er designet for å rute arbeid, ikke for å *utføre* alt selv. Compute-separasjon er en konfigurasjon, ikke en arkitekturendring. ## Spenninger og åpne spørsmål - **Synkron vs asynkron.** "Fang" og "lever" kan være instant, men "prosesser" kan ta sekunder (TTS) eller minutter (full episode- transkripsjon). Grensesnittet må håndtere begge naturlig. - **Strømmer.** Live lyd/video er fundamentalt annerledes enn request/response. Men edge-modellen løser mye: maskinrommet ser en stream-edge og vet at det betyr LiveKit. Utfordringen er *reaktivitet* — maskinrommet må observere edge-endringer i sanntid og koble inn/ut ressurser dynamisk. - **Granularitet.** Hvor mye skal maskinrommet vite om domenet? "Fang lyd" er generisk, men "transkriber og splitt i segmenter med taler-identifikasjon" er domenespesifikt. Hvor går grensen? - **Overhead.** Et ekstra lag betyr et ekstra kall. For tunge operasjoner (Whisper, LLM) er det neglisjerbart. For lette operasjoner (slå opp metadata) kan det være unødvendig indirection. ## Plassering i lagmodellen Maskinrommet (Rust) er det eneste orkestringslaget. Alle tjenester — inkludert PG, SpacetimeDB, CAS, Whisper, LiteLLM, LiveKit — er likeverdige tjenester *under* maskinrommet. SpacetimeDB er ikke et lag mellom Rust og GUI, det er en tjeneste maskinrommet skriver til. Ett unntak: SpacetimeDB har en direkte WebSocket-kobling til frontend for sanntids lese-strøm. Dette er en bevisst optimering — STDB sitt klient-SDK gir ~10μs-oppdateringer med automatisk synk og lokal cache. Å proxy dette gjennom Rust ville vært å bygge en dårligere versjon av noe STDB gjør optimalt. Se [datalaget](datalaget.md) for full lagmodell med diagram. ## Forhold til andre retninger Maskinrommet er infrastrukturen *under* de tre primitivene i [universell input og mottak](universell_input.md): - Input-primitiven kaller `fang()` + `prosesser()` - Mottak-primitiven kaller `lever()` - Kommunikasjonsnoden bruker alle tre (fang input fra deltakere, prosesser sanntid, lever til mottakere) Det er også det som gjør to-lags-modellen fra [rom, ikke forum](rom_ikke_forum.md) praktisk: maskinrommet ruter til riktig lag (sanntid vs tradisjonelt) uten at primitivene trenger å vite forskjellen.