server/docs/retninger/maskinrommet.md
vegard 8ca9832248 Legg til ops/ (vedlikeholdsjobber) og docs/retninger/ (arkitektoniske teser)
Ny mappe ops/ med repeterbare vedlikeholdsjobber:
- ryddejobb.md — full prosjektrevisjon
- doc-audit.md — docs vs kode
- drift-sjekk.md — prod vs lokal vs docs

Ny mappe docs/retninger/ med arkitektoniske teser:
- status_quo.md — hva Sidelinja er i dag
- rom_ikke_forum.md — opplevelse-først, to-lags-modell, administrativ opplevelse
- universell_input.md — tre primitiver (input, mottak, kommunikasjon), noder+edges
- maskinrommet.md — Rust-orkestrator, edge-drevet ressursorkestrering, CAS+pruning
- bruker_ikke_workspace.md — brukeren er sentrum, workspaces er samlings-noder
- datalaget.md — PG+Apache AGE, SpacetimeDB som sanntidslag, lagmodell

Oppdatert CLAUDE.md og proposals/README.md med referanser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 04:54:17 +01:00

13 KiB

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.

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 for full lagmodell med diagram.

Forhold til andre retninger

Maskinrommet er infrastrukturen under de tre primitivene i universell input og mottak:

  • 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 praktisk: maskinrommet ruter til riktig lag (sanntid vs tradisjonelt) uten at primitivene trenger å vite forskjellen.