Orkestrering: tre nivåer (script/fritekst/drøm) + rename portvokter→vaktmester

Orkestrering restrukturert med deklarativt script som primærnivå:
- Nivå 1: eksakte CLI-kall med {event.*}-variabler, ingen AI
- Nivå 2: fritekst tolket av bot med function calling
- Nivå 3: drømmemodus — bruker skriver fritt, mangler→work_items
- Auto-eskalering: script→bot ved uventet feil

Rename portvokter→vaktmester i alle docs — bedre navn for en
tjeneste som gjør ting, ikke bare sjekker legitimasjon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-18 15:59:29 +00:00
parent 68f7b10e9b
commit fd0b75ee13
7 changed files with 188 additions and 144 deletions

View file

@ -182,7 +182,7 @@ Kanal 2 er usynlig for brukeren. De ser bare svaret.
1. En bruker skriver i en kommunikasjonsnode der bot-agenten er
`member_of`.
2. Portvokteren trigger `agent_respond`-jobb (eksisterende flow).
2. Vaktmesteren trigger `agent_respond`-jobb (eksisterende flow).
3. Boten svarer i samtalen.
4. Boten vurderer: er noe her actionable?
5. Hvis ja: oppretter riktig node-type med riktig tag.
@ -213,7 +213,7 @@ Vegard triagerer innboksen og prioriterer.
### Ruting
`@bot` rutes av portvokteren basert på kontekst:
`@bot` rutes av vaktmesteren basert på kontekst:
- Standard: Claude (nåværende `agent_respond`-flyt)
- Fremtidig: spesialiserte agenter, andre modeller, eller
regelbaserte svar — uten endring i brukergrensesnittet

View file

@ -4,13 +4,13 @@
## 1. Konsept
Orkestreringer er noder som *gjør* ting. De er oppskrifter —
sekvenser av CLI-verktøy og bot-instruksjoner som utføres
automatisk når en trigger aktiveres. Brukeren skriver hva som
skal skje i naturlig språk. Boten tolker og utfører.
sekvenser av CLI-verktøy som utføres automatisk når en trigger
aktiveres. Tre nivåer: deklarativt script (ingen AI),
fritekst med AI-tolkning, og full drømmemodus.
## 2. Hvorfor
Synops har mange automatiseringer hardkodet i portvokteren:
Synops har mange automatiseringer hardkodet i vaktmesteren:
podcast-pipeline, AI-beriking, publisering. De er usynlige,
uendrelige og umulige å tilpasse for brukere.
@ -22,7 +22,7 @@ primitiver — synlige, redigerbare, delbare, versjonerbare.
```
node_kind: 'orchestration'
title: "Podcast: opptak → publisering"
content: "Når en innspilling avsluttes..."
content: <script eller fritekst>
metadata: {
"trigger": {
"event": "communication.ended",
@ -31,10 +31,9 @@ metadata: {
"has_media": "audio"
}
},
"executor": "bot",
"executor": "script" | "bot" | "dream",
"intelligence": 2,
"effort": 2,
"compiled": false
"effort": 2
}
```
@ -48,38 +47,95 @@ metadata: {
⚡ signaliserer: "denne noden gjør noe av seg selv."
## 4. Fritekst-instruksjoner
## 4. Tre utførelsesnivåer
Stegene i en orkestrering er naturlig språk:
### Nivå 1: Deklarativt script (ingen AI)
Eksakte CLI-kall med variabler fra trigger-konteksten.
Vaktmesteren parser og eksekverer direkte — ingen LLM.
```
NÅR innspilling.avsluttet
HVIS samling.har_trait("podcast")
1. synops-transcribe --cas-hash {event.cas_hash} --model large
VED_FEIL: synops-transcribe --cas-hash {event.cas_hash} --model medium
2. synops-summarize --communication-id {event.communication_id}
3. synops-rss --collection-id {event.collection_id}
VED_FEIL: work_item "Podcast-pipeline feilet" --tag bug
```
**Grammatikk:**
```
TRIGGER NÅR <event> [HVIS <betingelse>]
STEP <N>. <tool> <args...>
FALLBACK VED_FEIL: <tool> <args...> | work_item <title> [--tag <tag>]
VARIABLE {event.<felt>} — substitueres fra trigger-konteksten
```
Enkelt nok til å parse med en liten Rust-parser i vaktmesteren.
Deterministisk, gratis, raskt. **De fleste produksjons-
orkestreringer vil være på dette nivået.**
### Nivå 2: Fritekst med AI-tolkning
Naturlig språk som boten tolker og utfører steg for steg
med function calling. For orkestreringer der stegene ikke
er helt forutsigbare.
```
Når en innspilling i en samling med podcast-trait avsluttes:
1. Transkriber lydfilen med synops-transcribe (model: large)
2. Generer oppsummering med synops-summarize
1. Transkriber lydfilen med stor modell
2. Generer oppsummering
3. Foreslå kapitler basert på transkripsjonen
4. Generer show notes
5. Oppdater RSS-feed med synops-rss
5. Oppdater RSS-feed
Hvis transkribering feiler, prøv igjen med model: medium.
Hvis RSS feiler, opprett work_item med tag 'bug'.
Hvis transkribering feiler, prøv igjen med medium modell.
Hvis RSS feiler, opprett en oppgave.
```
Brukeren skriver hva de vil. Boten tolker instruksjonene og
utfører med function calling — kaller CLI-verktøy via
portvokteren (se `docs/infra/robusthet.md` § function calling).
Boten mapper "transkriber lydfilen" → `synops-transcribe`,
"oppsummer" → `synops-summarize`, osv. Krever LLM-kall
per kjøring (Haiku er nok for de fleste).
### Hvorfor fritekst, ikke DSL?
### Nivå 3: Drømmemodus
- Ingen DSL å lære for brukeren
- Boten resonnerer om feil og edge cases
- Feilhåndtering er naturlig ("hvis X feiler, prøv Y")
- Endring er å redigere tekst, ikke debugge JSON
Brukeren skriver hva de *ønsker*, uten å vite hvilke verktøy
som finnes. Boten prøver, og mangler som oppdages blir
feature requests.
```
Gjør episoden klar for publisering. Lag en lydfil med
sammendrag og send den til alle deltakere.
```
Boten sjekker tilgjengelige verktøy, gjør det den kan,
og oppretter work_items for det som mangler.
### Naturlig progresjon
```
Drømmemodus → Bruker beskriver ønsket resultat
↓ AI foreslår
Fritekst → Bruker justerer steg i naturlig språk
↓ AI foreslår kompilering etter N kjøringer
Script → Deklarative CLI-kall, ingen AI
↓ manuelt
Kode → Eget CLI-verktøy (synops-<verb>)
```
Hvert nivå kan fryses til nivået under. AI foreslår
kompilering, bruker godkjenner. Scriptet kan alltid
redigeres manuelt.
## 5. Strukturert trigger
Triggeren er den eneste strukturerte delen — portvokteren
evaluerer den effektivt uten LLM:
Triggeren er alltid strukturert — vaktmesteren evaluerer
den effektivt uten LLM, uavhengig av utførelsesnivå:
```jsonc
{
@ -114,11 +170,26 @@ Filtrerer triggeren ytterligere:
## 6. Utførelse
### Bot-modus (default)
### Script-modus (nivå 1)
```
Trigger aktiveres
→ Portvokteren finner matchende orchestration-node
→ Vaktmesteren finner matchende orchestration-node
→ Parser scriptet
→ Substituerer {event.*}-variabler fra trigger-kontekst
→ Utfører steg sekvensielt via generisk dispatch
(synops-{tool} --payload-json)
→ Ved feil: kjør VED_FEIL-steg eller opprett work_item
→ Logger i orchestration_log
```
Ingen LLM. Deterministisk. Raskt.
### Bot-modus (nivå 2)
```
Trigger aktiveres
→ Vaktmesteren finner matchende orchestration-node
→ Sender til bot med function calling:
- Trigger-kontekst (hvilken node, hvilken event)
- Instruksjonene fra orchestration.content
@ -128,39 +199,24 @@ Trigger aktiveres
→ Ved feil: resonnerer og prøver alternativ
```
### Kompilert modus (optimalisert)
### Drømmemodus (nivå 3)
Når en orkestrering har kjørt mange ganger med samme mønster,
kan den kompileres til en direkte pipeline:
Som bot-modus, men med høyere intelligens (Sonnet+) og
instruks om å opprette work_items for manglende verktøy.
```jsonc
{
"compiled": true,
"pipeline": [
{ "tool": "synops-transcribe", "args_map": {"cas_hash": "input.cas_hash", "model": "large"} },
{ "tool": "synops-summarize", "args_map": {"communication_id": "input.id"} },
{ "tool": "synops-rss", "args_map": {"collection_id": "input.collection_id"} }
],
"fallback": "bot"
}
```
### Auto-eskalering
Ingen LLM-kall for standardsteg. Direkte dispatch. Mye raskere,
billigere, deterministisk. `fallback: "bot"` betyr: ved feil
eller uventet situasjon, fall tilbake til fritekst-instruksjonene.
### Naturlig progresjon
Script-modus faller tilbake til bot-modus ved uventet feil:
```
1. Fritekst → Brukere skriver, boten tolker
2. Observér → Boten logger hvilke verktøy/sekvenser som gjentas
3. Foreslå → "Denne har kjørt 50 ganger. Kompilere?"
4. Kompilér → Fast pipeline, ingen LLM for standardsteg
5. DSL (senere) → Kun hvis kompilerte pipelines trenger mer uttrykk
Script kjører steg 2
→ synops-summarize returnerer exit 1
→ VED_FEIL er definert → prøv alternativ
→ Alternativ feiler også
→ Eskalér til bot-modus for dette steget
→ Boten resonnerer om feilen og prøver å løse det
```
DSL-en designes ikke nå — den oppstår fra observerte mønstre.
## 7. Koblinger mellom orkestreringer
Orkestreringer er noder med edges:
@ -181,29 +237,36 @@ Kaskade via edges, ikke hardkodet.
│ │
│ Trigger: [Innspilling avsluttet ▼] │
│ Betingelse: [Samling har podcast-trait ▼] │
│ Modus: [Script ▼] │
│ │
│ Steg: │
│ ┌─────────────────────────────────────┐ │
│ │ Transkriber lydfilen med stor │ │
│ │ modell. Hvis det feiler, bruk │ │
│ │ medium. │ │
│ ├─────────────────────────────────────┤ │
│ │ Oppsummer samtalen. │ │
│ ├─────────────────────────────────────┤ │
│ │ Foreslå kapitler. │ │
│ ├─────────────────────────────────────┤ │
│ │ Oppdater RSS-feed. │ │
│ │ NÅR innspilling.avsluttet │ │
│ │ HVIS samling.har_trait("podcast") │ │
│ │ │ │
│ │ 1. synops-transcribe │ │
│ │ --cas-hash {event.cas_hash} │ │
│ │ --model large │ │
│ │ VED_FEIL: ... --model medium │ │
│ │ 2. synops-summarize │ │
│ │ --communication-id {event.id} │ │
│ │ 3. synops-rss │ │
│ │ --collection-id {event.coll_id} │ │
│ └─────────────────────────────────────┘ │
│ │
│ [+ Legg til steg] [▶ Test kjøring] │
│ [▶ Test kjøring] [↑ Konverter til AI] │
│ │
│ Status: Aktiv Kjørt: 47 ganger │
│ Sist: 2026-03-18 12:30 (OK) │
│ │
Tilgjengelig: synops-transcribe, │
│ synops-rss, synops-render... │
│ (12 verktøy) │
└───────────────────────────────────────────┘
```
Hvert steg er en tekstboks. "Test kjøring" sender til boten
med dry-run. Historikk synlig.
Modusvelger: Script / Fritekst / Drømmemodus.
"Test kjøring" utfører med dry-run.
"Konverter til AI" løfter scriptet til fritekst-modus.
## 9. Edge-modell
@ -226,12 +289,13 @@ Orkestreringen peker på det den observerer:
```
Legg til `observes`-edge → aktivert for den noden.
Fjern edge → deaktivert. Samme orkestrering kan observere mange noder.
Fjern edge → deaktivert. Opprettet via drag-and-drop
(se `docs/retninger/interaksjonsmodell.md`).
### Implisitt vs eksplisitt
- **`observes`-edge:** Eksplisitt. "Denne orkestreringen overvåker
denne noden." Opprettet via drag-and-drop (se § interaksjonsmodell).
denne noden."
- **Trigger-betingelser:** Implisitt. "Overvåk alt som matcher."
- `observes` overtrumfer: fjernet `observes`-edge betyr "ikke her",
selv om betingelsene matcher. Brukeren har kontroll.
@ -241,8 +305,6 @@ Fjern edge → deaktivert. Samme orkestrering kan observere mange noder.
Brukeren begrenses ikke til kjente verktøy. De skriver fritt
— boten prøver, og mangler som oppdages blir feature requests.
### Slik fungerer det
```
Bruker skriver: "Lag en lydfil med sammendrag og send til deltakerne"
@ -250,7 +312,7 @@ Boten sjekker:
✓ synops-summarize finnes → oppsummerer
✗ synops-tts finnes ikke → kan ikke lage lyd
Boten svarer i chatten:
Boten svarer:
"Jeg oppsummerte møtet, men Synops har ikke tekst-til-tale
ennå. Jeg oppretter en forespørsel?"
@ -260,50 +322,32 @@ Boten svarer i chatten:
source_material → orkestreringsnoden
```
Hver feilet steg som skyldes manglende verktøy er en **feature
request** — gratis, kontekstuell, fra faktisk behov. Systemet
lærer hva brukerne vil ha fra orkestreringer som ikke lykkes.
### Diskret hint, ikke begrensning
I editoren vises tilgjengelige verktøy som hint, men brukeren
kan skrive hva som helst:
```
┌─ Steg 3 ──────────────────────────────────┐
│ Lag en lydfil med oppsummeringen og send │
│ den til alle deltakere │
│ │
Tilgjengelig: synops-tts, synops-rss, │
│ synops-render, synops-transcribe... │
│ (12 verktøy) — men skriv hva du vil │
└────────────────────────────────────────────┘
```
Brukeren drømmer. Systemet vokser.
Systemet lærer hva brukerne vil ha fra det som ikke lykkes.
## 11. Avgrensning
- Orkestreringer er **ikke** en generell workflow-engine.
De er oppskrifter som boten følger.
- Fritekst-instruksjoner er primær. Kompilering er
optimalisering, ikke krav.
- Triggere evalueres av portvokteren, utførelse av boten.
Klar ansvarsfordeling.
- Feilhåndtering er botens ansvar. Ingen retry-DSL.
De er oppskrifter — deklarative eller AI-tolkede.
- Deklarativt script er primær for produksjon. AI er for
oppretting, feilhåndtering og drømmemodus.
- Triggere evalueres av vaktmesteren, utførelse av
vaktmesteren (script) eller bot (fritekst/drøm).
- Brukeren begrenses aldri til kjente verktøy. Manglende
funksjonalitet fanges opp som feature requests.
## 12. Komponenter
| Feature | Rolle |
|---------|-------|
| Portvokteren | Evaluerer triggere, dispatcher til bot |
| @bot | Tolker instruksjoner, utfører med function calling |
| Vaktmesteren | Evaluerer triggere, parser/utfører scripts, dispatcher til bot |
| @bot | Tolker fritekst-instruksjoner, utfører med function calling |
| CLI-verktøy | Gjør det faktiske arbeidet |
| Arbeidstavlen | Work items opprettes ved feil |
| Responskvalitet | Intelligence/effort per orkestrering |
## 13. Bygger på
- `docs/retninger/unix_filosofi.md` — CLI-verktøy som byggeklosser
- `docs/retninger/interaksjonsmodell.md` — drag-and-drop for observes-edge
- `docs/concepts/arbeidstavlen.md`@bot, work items ved feil
- `docs/infra/robusthet.md` — function calling, fallback
- `docs/features/responskvalitet.md` — intelligence/effort-nivåer

View file

@ -41,7 +41,7 @@ For hver tjeneste:
- Avhengigheter
- Healthcheck
Tjenester: Caddy, portvokteren, SvelteKit, PostgreSQL,
Tjenester: Caddy, vaktmesteren, SvelteKit, PostgreSQL,
Authentik, LiteLLM, faster-whisper, LiveKit.
### 4. CLI-verktøy
@ -77,7 +77,7 @@ men hvorfor det er bygget slik:
- **Alt er noder og edges.** Ingen separate tabeller. Visninger
er spørringer mot grafen.
- **Unix-filosofi.** Portvokteren orkestrerer, CLI-verktøy gjør
- **Unix-filosofi.** Vaktmesteren orkestrerer, CLI-verktøy gjør
jobben. Én ting, gjort godt. Delt verktøykasse.
- **Selvdokumenterende system.** Systemet dokumenterer seg selv
som noder i seg selv. Docs er ikke filer — de er graf-data.
@ -87,7 +87,7 @@ men hvorfor det er bygget slik:
by design, ikke konfigurasjon.
- **Noder er sentrum.** Brukere, team, innhold — alt er noder.
Tilgang via materialisert tilgangsmatrise fra edges.
- **Portvokteren er tynn.** Auth, HTTP-ruting, STDB-synk.
- **Vaktmesteren er tynn.** Auth, HTTP-ruting, STDB-synk.
All logikk i CLI-verktøy og synops-common.
- **Generisk dispatch.** `synops-{job_type} --payload-json`.
Nytt verktøy = binary i PATH. Ingen rekompilering.
@ -108,7 +108,7 @@ Nummerert sekvens fra ren server til kjørende system:
4. Start Docker-tjenester (PG, Authentik, LiteLLM, Whisper, LiveKit)
5. Kjør PG-migrasjoner (skjema + seed)
6. Konfigurer Authentik (OIDC-provider, redirect URIs)
7. Bygg og deploy portvokteren (cargo build, systemd)
7. Bygg og deploy vaktmesteren (cargo build, systemd)
8. Bygg og deploy SvelteKit (npm build, systemd)
9. Bygg CLI-verktøy (cargo build per tool)
10. Konfigurer Caddy (Caddyfile, reload)

View file

@ -5,7 +5,7 @@
```
Lag 1: LLM-leverandør (Claude API, OpenAI, etc.) → utenfor vår kontroll
Lag 2: LiteLLM (AI Gateway) → lokal Docker
Lag 3: Portvokteren → lokal native (systemd)
Lag 3: Vaktmesteren → lokal native (systemd)
Lag 4: CLI-verktøy (synops-*) → per-kall, isolert
Lag 5: PostgreSQL → lokal Docker, SPOF
```
@ -14,11 +14,11 @@ Lag 5: PostgreSQL → lokal Docker, SPOF
### Bot-nedetid
Portvokteren overvåker `synops-respond` sin exit-kode. Ved feil:
Vaktmesteren overvåker `synops-respond` sin exit-kode. Ved feil:
```
@bot (ingen respons på 30 sek)
Portvokteren sender automatisk i chatten:
Vaktmesteren sender automatisk i chatten:
"⚠ Boten er midlertidig utilgjengelig.
Meldingen din er lagret og blir besvart
når tjenesten er tilbake."
@ -31,12 +31,12 @@ kan den prosessere ubesvarte meldinger.
### Healthcheck
Portvokteren eksponerer `/health` som sjekker:
Vaktmesteren eksponerer `/health` som sjekker:
- PG-tilkobling
- LiteLLM-tilgjengelighet
- Disk-status
Systemd restarter portvokteren ved gjentatte feil.
Systemd restarter vaktmesteren ved gjentatte feil.
## LLM fallback-kjede
@ -53,18 +53,18 @@ synops-respond → LiteLLM
Brukeren merker kanskje kvalitetsforskjell, men tjenesten er
oppe. Fallback-kjeden konfigureres i LiteLLM — ingen endring
i portvokteren eller synops-respond.
i vaktmesteren eller synops-respond.
## Ekstern API-bot og CLI-verktøy
En LLM via API har ikke shell-tilgang. Den trenger det heller
ikke — portvokteren er mellomleddet.
ikke — vaktmesteren er mellomleddet.
### Function calling / tool use
```
Bruker: "@bot sjekk om RSS-feeden oppdaterte seg"
portvokteren → synops-respond → LLM API
vaktmesteren → synops-respond → LLM API
LLM svarer med structured output:
{
@ -75,21 +75,21 @@ LLM svarer med structured output:
]
}
Portvokteren:
Vaktmesteren:
1. Poster chat-svaret i samtalen
2. Spawner: synops-rss --payload-json '{"collection_id":"abc123"}'
3. Poster resultatet som oppfølgingsmelding
```
LLM-en beskriver *hva* som skal gjøres. Portvokteren *gjør*
det. Navnekonvensjonen (`synops-{tool}`) betyr at portvokteren
LLM-en beskriver *hva* som skal gjøres. Vaktmesteren *gjør*
det. Navnekonvensjonen (`synops-{tool}`) betyr at vaktmesteren
kan dispatche uten hardkodet mapping — samme generiske dispatch
som jobbkøen (se `docs/retninger/unix_filosofi.md`).
### Tilgangskontroll for actions
Ikke alle brukere skal kunne trigge alle verktøy via `@bot`.
Portvokteren sjekker:
Vaktmesteren sjekker:
1. Brukerens rolle i konteksten (owner/admin/member/reader)
2. Verktøyets tilgangsnivå (fra `cli_tool`-nodens metadata)
@ -104,9 +104,9 @@ En `reader` kan spørre `@bot` om informasjon, men ikke trigge
|-----------|-----------|----------|------------------|
| Claude API nede | LiteLLM timeout | Neste modell i kjeden | Svarer, kanskje litt dårligere |
| Alle LLM-er nede | synops-respond exit != 0 | Statisk "utilgjengelig" + work_item | Vet at meldingen er mottatt |
| Portvokteren nede | Systemd healthcheck → restart | CLI fungerer for Claude Code | Web-brukere venter, terminal funker |
| Vaktmesteren nede | Systemd healthcheck → restart | CLI fungerer for Claude Code | Web-brukere venter, terminal funker |
| PG nede | Connection refused | Alt stopper | Eneste reelle SPOF |
| WebSocket nede | Portvokteren restarter | Frontend rekobler automatisk | Sanntid midlertidig borte, data trygt |
| WebSocket nede | Vaktmesteren restarter | Frontend rekobler automatisk | Sanntid midlertidig borte, data trygt |
## PG som eneste SPOF
@ -123,7 +123,7 @@ Mitigering:
## Onboarding som statisk dispatch
Første `@bot` i en ny brukers velkomst-chat trenger ikke
LLM-roundtrip. Portvokteren gjenkjenner mønsteret og serverer
LLM-roundtrip. Vaktmesteren gjenkjenner mønsteret og serverer
onboarding-noden direkte:
```rust
@ -141,6 +141,6 @@ Etter første melding fungerer chatten normalt med LLM.
## Bygger på
- `docs/infra/ai_gateway.md` — LiteLLM fallback-kjeder
- `docs/retninger/unix_filosofi.md` — generisk dispatch
- `docs/retninger/maskinrommet.md`portvokter-rollen
- `docs/retninger/maskinrommet.md`vaktmester-rollen
- `docs/concepts/arbeidstavlen.md`@bot-konvensjonen
- `docs/concepts/selvdokumenterende_system.md` — onboarding-node

View file

@ -3,7 +3,7 @@
**Status: Besluttet. Revidert mars 2026 — SpacetimeDB fjernet.**
> PostgreSQL er eneste datakilde. Sanntid via PG `LISTEN/NOTIFY`
> og WebSocket i portvokteren. CAS lagrer binærdata.
> og WebSocket i vaktmesteren. CAS lagrer binærdata.
> Apache AGE legges til ved behov for Cypher-traverseringer.
## Lagmodell
@ -12,29 +12,29 @@
GUI (SvelteKit)
│ skriv │ les (sanntid, WebSocket)
▼ ▼
Portvokteren (Rust) Portvokteren ──WebSocket──→ GUI
Vaktmesteren (Rust) Vaktmesteren ──WebSocket──→ GUI
│ validering ▲
└──→ PostgreSQL ──NOTIFY──→──┘
```
### Skrivestien
GUI → portvokteren → validering → PG. Frontend oppdateres via
GUI → vaktmesteren → validering → PG. Frontend oppdateres via
WebSocket-push utløst av PG NOTIFY.
### Lesestien (sanntid)
PG → portvokteren → WebSocket → GUI.
Portvokteren holder en in-memory cache av aktive subscriptions
PG → vaktmesteren → WebSocket → GUI.
Vaktmesteren holder en in-memory cache av aktive subscriptions
og pusher relevante endringer til tilkoblede klienter.
### Lesestien (tunge spørringer)
GUI → portvokteren → PG.
GUI → vaktmesteren → PG.
Fulltekstsøk, pgvector, statistikk, AGE-traverseringer.
## PostgreSQL — eneste datakilde
Én sannhetskilde. Ingen synk, ingen konsistensproblemer:
- **Sanntid:** `LISTEN/NOTIFY`portvokteren → WebSocket
- **Sanntid:** `LISTEN/NOTIFY`vaktmesteren → WebSocket
- **Fulltekstsøk:** `tsvector``nodes.content` og `nodes.title`
- **Semantisk søk:** pgvector for embedding-basert likhet
- **Graftraversering:** rekursive CTEs, Apache AGE ved behov
@ -63,11 +63,11 @@ Se [maskinrommet](maskinrommet.md).
## Sanntid via PG LISTEN/NOTIFY
PG har innebygd pub/sub. Portvokteren lytter og videresender:
PG har innebygd pub/sub. Vaktmesteren lytter og videresender:
```
PG: NOTIFY node_changed, '{"id":"abc","kind":"content"}'
Portvokteren mottar
Vaktmesteren mottar
→ Sjekker tilgangsmatrise: hvem skal se denne endringen?
→ Pusher til relevante WebSocket-tilkoblinger
→ Frontend oppdaterer reaktivt
@ -97,9 +97,9 @@ CREATE TRIGGER nodes_notify
Tilsvarende for edges.
### WebSocket i portvokteren
### WebSocket i vaktmesteren
Portvokteren holder:
Vaktmesteren holder:
- Map av tilkoblede klienter → brukerens node_id
- Map av node_id → synlige noder (fra node_access)
- Ved NOTIFY: filtrer på tilgang, push til relevante klienter
@ -130,6 +130,6 @@ lærdommer: `docs/erfaringer/spacetimedb_integrasjon.md`.
beregnet fra edge-grafen, brukes for WebSocket-filtrering
- [Universell input og mottak](universell_input.md) — noder og edges
er datamodellen for alle tre primitiver
- [Maskinrommet / Portvokteren](maskinrommet.md) — CAS-pruning,
- [Maskinrommet / Vaktmesteren](maskinrommet.md) — CAS-pruning,
edge-drevet ressursorkestrering, validering før skriving,
WebSocket-endepunkt for sanntid

View file

@ -181,7 +181,7 @@ samme maskin.
Compute-separasjon er en konfigurasjon, ikke en arkitekturendring.
## Evolusjon: Maskinrommet → Portvokteren
## Evolusjon: Maskinrommet → Vaktmesteren
Maskinrommet ble bygget som en monolitt — auth, validering,
prosessering, jobbkø i én binær. Med unix-filosofi-
@ -193,20 +193,20 @@ prosessering til CLI-verktøy. Det som blir igjen er:
3. **Sanntid** — PG LISTEN/NOTIFY → WebSocket til frontend
4. **Jobbkø-dispatch** — poll PG, spawn CLI-verktøy
Dette er en **portvokter**, ikke et maskinrom. Når uttynningen er
ferdig, renames `maskinrommet/` til `portvokteren/` og systemd-
Dette er en **vaktmester**, ikke et maskinrom. Når uttynningen er
ferdig, renames `maskinrommet/` til `vaktmesteren/` og systemd-
tjenesten oppdateres. Navnet skal reflektere rollen: vokter porten,
gjør ikke jobben.
**Hva dette gir:**
- Portvokteren dør → frontend stopper, men CLI-verktøy fungerer.
- Vaktmesteren dør → frontend stopper, men CLI-verktøy fungerer.
Claude jobber videre, scripts kjører, terminalen funker.
- Et CLI-verktøy dør → bare den funksjonen stopper. Alt annet
er upåvirket.
- Portvokteren blir så enkel at den nesten aldri feiler.
- Vaktmesteren blir så enkel at den nesten aldri feiler.
**Edge-validering og tilgangskontroll** flyttes til `synops-common`
(delt lib) — brukes av både portvokteren og CLI-verktøy.
(delt lib) — brukes av både vaktmesteren og CLI-verktøy.
## Forhold til andre retninger

View file

@ -69,7 +69,7 @@ synops-transcribe --cas-hash abc123 --model medium
- **Navnekonvensjon:** `synops-<verb>` (f.eks. `synops-transcribe`)
- **Input:** args + stdin + env-variabler (DATABASE_URL, CAS_ROOT)
- **Payload-modus:** `--payload-json <json>` for jobbkø-dispatch
(verktøyet parser selv, portvokteren trenger ikke kjenne argumentene)
(verktøyet parser selv, vaktmesteren trenger ikke kjenne argumentene)
- **Output:** stdout (strukturert — JSON eller markdown)
- **Feilhåndtering:** stderr for feilmeldinger, exit-kode != 0 ved feil
- **Ingen tilstandsendring uten flagg:** lesing er default, skriving krever
@ -78,7 +78,7 @@ synops-transcribe --cas-hash abc123 --model medium
## Generisk dispatch
Portvokteren trenger null konfigurasjon per verktøy. Bare en
Vaktmesteren trenger null konfigurasjon per verktøy. Bare en
navnekonvensjon:
```rust
@ -89,10 +89,10 @@ cmd.arg("--payload-json").arg(&job.payload_json);
`job_type: "transcribe"``synops-transcribe --payload-json '{...}'`.
Verktøyet parser payload selv. Nytt verktøy = legg binary i PATH,
bruk riktig `job_type` i køen. Ingen rekompilering av portvokteren.
bruk riktig `job_type` i køen. Ingen rekompilering av vaktmesteren.
`cli_tool`-noder i PG bærer metadata (timeout, cpu_weight,
dokumentasjon) — men ikke dispatch-logikk. Portvokteren leser
dokumentasjon) — men ikke dispatch-logikk. Vaktmesteren leser
timeout og cpu_weight fra noden, men selve invokasjon er
konvensjonsbasert.