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

View file

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

View file

@ -41,7 +41,7 @@ For hver tjeneste:
- Avhengigheter - Avhengigheter
- Healthcheck - Healthcheck
Tjenester: Caddy, portvokteren, SvelteKit, PostgreSQL, Tjenester: Caddy, vaktmesteren, SvelteKit, PostgreSQL,
Authentik, LiteLLM, faster-whisper, LiveKit. Authentik, LiteLLM, faster-whisper, LiveKit.
### 4. CLI-verktøy ### 4. CLI-verktøy
@ -77,7 +77,7 @@ men hvorfor det er bygget slik:
- **Alt er noder og edges.** Ingen separate tabeller. Visninger - **Alt er noder og edges.** Ingen separate tabeller. Visninger
er spørringer mot grafen. 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. jobben. Én ting, gjort godt. Delt verktøykasse.
- **Selvdokumenterende system.** Systemet dokumenterer seg selv - **Selvdokumenterende system.** Systemet dokumenterer seg selv
som noder i seg selv. Docs er ikke filer — de er graf-data. 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. by design, ikke konfigurasjon.
- **Noder er sentrum.** Brukere, team, innhold — alt er noder. - **Noder er sentrum.** Brukere, team, innhold — alt er noder.
Tilgang via materialisert tilgangsmatrise fra edges. 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. All logikk i CLI-verktøy og synops-common.
- **Generisk dispatch.** `synops-{job_type} --payload-json`. - **Generisk dispatch.** `synops-{job_type} --payload-json`.
Nytt verktøy = binary i PATH. Ingen rekompilering. 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) 4. Start Docker-tjenester (PG, Authentik, LiteLLM, Whisper, LiveKit)
5. Kjør PG-migrasjoner (skjema + seed) 5. Kjør PG-migrasjoner (skjema + seed)
6. Konfigurer Authentik (OIDC-provider, redirect URIs) 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) 8. Bygg og deploy SvelteKit (npm build, systemd)
9. Bygg CLI-verktøy (cargo build per tool) 9. Bygg CLI-verktøy (cargo build per tool)
10. Konfigurer Caddy (Caddyfile, reload) 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 1: LLM-leverandør (Claude API, OpenAI, etc.) → utenfor vår kontroll
Lag 2: LiteLLM (AI Gateway) → lokal Docker 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 4: CLI-verktøy (synops-*) → per-kall, isolert
Lag 5: PostgreSQL → lokal Docker, SPOF Lag 5: PostgreSQL → lokal Docker, SPOF
``` ```
@ -14,11 +14,11 @@ Lag 5: PostgreSQL → lokal Docker, SPOF
### Bot-nedetid ### 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) @bot (ingen respons på 30 sek)
Portvokteren sender automatisk i chatten: Vaktmesteren sender automatisk i chatten:
"⚠ Boten er midlertidig utilgjengelig. "⚠ Boten er midlertidig utilgjengelig.
Meldingen din er lagret og blir besvart Meldingen din er lagret og blir besvart
når tjenesten er tilbake." når tjenesten er tilbake."
@ -31,12 +31,12 @@ kan den prosessere ubesvarte meldinger.
### Healthcheck ### Healthcheck
Portvokteren eksponerer `/health` som sjekker: Vaktmesteren eksponerer `/health` som sjekker:
- PG-tilkobling - PG-tilkobling
- LiteLLM-tilgjengelighet - LiteLLM-tilgjengelighet
- Disk-status - Disk-status
Systemd restarter portvokteren ved gjentatte feil. Systemd restarter vaktmesteren ved gjentatte feil.
## LLM fallback-kjede ## LLM fallback-kjede
@ -53,18 +53,18 @@ synops-respond → LiteLLM
Brukeren merker kanskje kvalitetsforskjell, men tjenesten er Brukeren merker kanskje kvalitetsforskjell, men tjenesten er
oppe. Fallback-kjeden konfigureres i LiteLLM — ingen endring 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 ## Ekstern API-bot og CLI-verktøy
En LLM via API har ikke shell-tilgang. Den trenger det heller En LLM via API har ikke shell-tilgang. Den trenger det heller
ikke — portvokteren er mellomleddet. ikke — vaktmesteren er mellomleddet.
### Function calling / tool use ### Function calling / tool use
``` ```
Bruker: "@bot sjekk om RSS-feeden oppdaterte seg" Bruker: "@bot sjekk om RSS-feeden oppdaterte seg"
portvokteren → synops-respond → LLM API vaktmesteren → synops-respond → LLM API
LLM svarer med structured output: LLM svarer med structured output:
{ {
@ -75,21 +75,21 @@ LLM svarer med structured output:
] ]
} }
Portvokteren: Vaktmesteren:
1. Poster chat-svaret i samtalen 1. Poster chat-svaret i samtalen
2. Spawner: synops-rss --payload-json '{"collection_id":"abc123"}' 2. Spawner: synops-rss --payload-json '{"collection_id":"abc123"}'
3. Poster resultatet som oppfølgingsmelding 3. Poster resultatet som oppfølgingsmelding
``` ```
LLM-en beskriver *hva* som skal gjøres. Portvokteren *gjør* LLM-en beskriver *hva* som skal gjøres. Vaktmesteren *gjør*
det. Navnekonvensjonen (`synops-{tool}`) betyr at portvokteren det. Navnekonvensjonen (`synops-{tool}`) betyr at vaktmesteren
kan dispatche uten hardkodet mapping — samme generiske dispatch kan dispatche uten hardkodet mapping — samme generiske dispatch
som jobbkøen (se `docs/retninger/unix_filosofi.md`). som jobbkøen (se `docs/retninger/unix_filosofi.md`).
### Tilgangskontroll for actions ### Tilgangskontroll for actions
Ikke alle brukere skal kunne trigge alle verktøy via `@bot`. Ikke alle brukere skal kunne trigge alle verktøy via `@bot`.
Portvokteren sjekker: Vaktmesteren sjekker:
1. Brukerens rolle i konteksten (owner/admin/member/reader) 1. Brukerens rolle i konteksten (owner/admin/member/reader)
2. Verktøyets tilgangsnivå (fra `cli_tool`-nodens metadata) 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 | | 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 | | 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 | | 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 ## PG som eneste SPOF
@ -123,7 +123,7 @@ Mitigering:
## Onboarding som statisk dispatch ## Onboarding som statisk dispatch
Første `@bot` i en ny brukers velkomst-chat trenger ikke 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: onboarding-noden direkte:
```rust ```rust
@ -141,6 +141,6 @@ Etter første melding fungerer chatten normalt med LLM.
## Bygger på ## Bygger på
- `docs/infra/ai_gateway.md` — LiteLLM fallback-kjeder - `docs/infra/ai_gateway.md` — LiteLLM fallback-kjeder
- `docs/retninger/unix_filosofi.md` — generisk dispatch - `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/arbeidstavlen.md`@bot-konvensjonen
- `docs/concepts/selvdokumenterende_system.md` — onboarding-node - `docs/concepts/selvdokumenterende_system.md` — onboarding-node

View file

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

View file

@ -181,7 +181,7 @@ samme maskin.
Compute-separasjon er en konfigurasjon, ikke en arkitekturendring. Compute-separasjon er en konfigurasjon, ikke en arkitekturendring.
## Evolusjon: Maskinrommet → Portvokteren ## Evolusjon: Maskinrommet → Vaktmesteren
Maskinrommet ble bygget som en monolitt — auth, validering, Maskinrommet ble bygget som en monolitt — auth, validering,
prosessering, jobbkø i én binær. Med unix-filosofi- 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 3. **Sanntid** — PG LISTEN/NOTIFY → WebSocket til frontend
4. **Jobbkø-dispatch** — poll PG, spawn CLI-verktøy 4. **Jobbkø-dispatch** — poll PG, spawn CLI-verktøy
Dette er en **portvokter**, ikke et maskinrom. Når uttynningen er Dette er en **vaktmester**, ikke et maskinrom. Når uttynningen er
ferdig, renames `maskinrommet/` til `portvokteren/` og systemd- ferdig, renames `maskinrommet/` til `vaktmesteren/` og systemd-
tjenesten oppdateres. Navnet skal reflektere rollen: vokter porten, tjenesten oppdateres. Navnet skal reflektere rollen: vokter porten,
gjør ikke jobben. gjør ikke jobben.
**Hva dette gir:** **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. Claude jobber videre, scripts kjører, terminalen funker.
- Et CLI-verktøy dør → bare den funksjonen stopper. Alt annet - Et CLI-verktøy dør → bare den funksjonen stopper. Alt annet
er upåvirket. 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` **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 ## Forhold til andre retninger

View file

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