synops/docs/concepts/orkestrering.md
vegard 1f21f90f76 Seed-orkestreringer og flerords-verbstøtte (oppgave 24.9)
Fem standard-orkestreringer opprettet som seed-data:
- Podcast-pipeline (transkriber → oppsummer → RSS)
- Publiseringsflyt (render → indeks → RSS)
- AI-beriking (foreslå koblinger ved nytt innhold)
- Planlagt publisering (render ved tidspunkt)
- Podcast TTS (kaskade fra pipeline → les opp oppsummering)

Podcast-pipeline → TTS demonstrerer kaskade via triggers-edge.

Script-kompilatoren utvidet med flerords-verbstøtte: aliaser som
"generer feed", "les opp", "foreslå koblinger" matcher nå korrekt
selv om parseren splitter ved første mellomrom. Prøver verb + N
første ord av objekt opptil 3 ord.
2026-03-18 18:11:02 +00:00

20 KiB
Raw Permalink Blame History

Konsept: Orkestrering

Filsti: docs/concepts/orkestrering.md

1. Konsept

Orkestreringer er noder som gjør ting. De er oppskrifter — 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 vaktmesteren: podcast-pipeline, AI-beriking, publisering. De er usynlige, uendrelige og umulige å tilpasse for brukere.

Orkestreringer gjør automatisering til førsteklasses graf- primitiver — synlige, redigerbare, delbare, versjonerbare.

3. Nodemodell

node_kind: 'orchestration'
title: "Podcast: opptak → publisering"
content: <script eller fritekst>
metadata: {
  "trigger": {
    "event": "communication.ended",
    "conditions": {
      "has_trait": "podcast",
      "has_media": "audio"
    }
  },
  "executor": "script" | "bot" | "dream",
  "intelligence": 2,
  "effort": 2
}

Tre typer noder som gjør

node_kind Ikon Aktivering Eksempel
cli_tool 🔧 Kalles av andre synops-transcribe
ai_preset 🔧 Kalles av AI-verktøy "Rens tekst"-prompt
orchestration Trigger-drevet, utfører seg selv Podcast-pipeline

signaliserer: "denne noden gjør noe av seg selv."

4. To lag: menneskelig språk og teknisk script

Orkestreringer har to representasjoner av samme oppskrift — et menneskelig lesbart språk som kompileres til tekniske CLI-kall. Brukeren skriver og leser det ene, vaktmesteren kjører det andre.

Menneskelig lag (bruker skriver)

NÅR innspilling avsluttet
HVIS samling har podcast

1. transkriber lydfilen (stor modell)
   ved feil: transkriber lydfilen (medium modell)
2. oppsummer samtalen
3. oppdater rss-feed

ved feil: opprett oppgave "Pipeline feilet" (bug)

Ingen --cas-hash, ingen {event.*}, ingen CLI-syntax. Norske verb som matcher verktøy. Argumenter i parenteser. Lesbart for alle.

Teknisk lag (vaktmesteren kjører)

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

Generert automatisk fra det menneskelige laget via kompilatoren. Deterministisk, gratis, raskt.

Kompilatoren

Vaktmesteren kompilerer menneskelig → teknisk ved lagring. Matchingen bruker cli_tool-noders metadata:

{
  "binary": "synops-transcribe",
  "aliases": ["transkriber", "transkribering"],
  "description": "Whisper-transkribering av lydfil",
  "args_hints": {
    "lydfilen": "{event.cas_hash}",
    "stor modell": "--model large",
    "medium modell": "--model medium"
  }
}

"transkriber lydfilen (stor modell)" matcher:

  • "transkriber" → alias for synops-transcribe
  • "lydfilen" → {event.cas_hash} (fra trigger-kontekst)
  • "(stor modell)" → --model large

Kompileringsfeil (Rust-stil)

Ved feil får brukeren presise, hjelpsomme meldinger:

┌─ Kompilering ────────────────────────────────┐
│                                              │
│  ✓ Linje 1: transkriber lydfilen (stor)      │
│    → synops-transcribe --model large         │
│                                              │
│  ✗ Linje 2: send epost til deltakerne        │
│    Feil: "send epost" matcher ingen verktøy  │
│    Mente du: "varsle deltakerne"?            │
│    Tilgjengelig: varsle, oppsummer, publiser  │
│                                              │
│  ✓ Linje 3: oppdater rss-feed               │
│    → synops-rss                              │
│                                              │
│  1 feil. Rett opp og prøv igjen.            │
└──────────────────────────────────────────────┘

Tre visninger i editoren

[Enkel]  [Teknisk]  [Kompilert]
Visning Hva Hvem
Enkel Norsk, lesbart Brukeren skriver her
Teknisk CLI-kall med variabler For de som vil se
Kompilert JSON/intern Skjult som default

Tabbar i editoren — som raw/rendered i tekst-editoren. Kompileringsfeil vises i sanntid mens brukeren skriver.

Grammatikk (menneskelig lag)

TRIGGER    NÅR <event i naturlig språk>
           HVIS <betingelse i naturlig språk>
STEP       <N>. <verb> <objekt> [(<argument>)]
FALLBACK   ved feil: <verb> <objekt> | opprett oppgave <tittel> [(<tag>)]

Grammatikk (teknisk lag)

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

De fleste produksjonsorkestreringer skrives og vedlikeholdes i det menneskelige laget. Det tekniske laget er generert.

Nivå 2: AI-assistert oppretting

Brukeren beskriver hva de vil i naturlig språk. AI genererer et deklarativt script (nivå 1) som vaktmesteren validerer. AI brukes ved oppretting, ikke ved kjøring.

Bruker: "Gjør episoden klar for publisering"

  → synops-ai genererer script-forslag:

    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 "Pipeline feilet" --tag bug

  → Vaktmesteren validerer:
    ✓ Alle verktøy finnes
    ✓ Alle variabler er gyldige
    ✓ Syntaks er korrekt

  → Bruker godkjenner eller justerer
  → Lagres som orchestration-node (nivå 1)

Etter oppretting kjører scriptet uten AI — deterministisk, gratis, raskt. AI-kostnaden er én gang, ved oppretting.

Nivå 3: Drømmemodus

Brukeren skriver hva de ønsker, uten å vite hvilke verktøy som finnes. AI prøver å generere et script, og mangler som oppdages blir feature requests.

Bruker: "Lag en lydfil med sammendrag og send til deltakerne"

  → synops-ai genererer:
    1. synops-summarize --communication-id {event.communication_id}
    2. synops-tts ???  ← finnes ikke

  → Vaktmesteren validerer:
    ✗ synops-tts finnes ikke

  → Tilbake til bruker:
    "Scriptet refererer til synops-tts som ikke finnes.
     Jeg oppretter en forespørsel om TTS-verktøy."
    → work_item "TTS for møteoppsummeringer" --tag feature
    → Delvis script lagres med synops-tts markert som manglende

Systemprompt for script-generering

AI-modellen trenger kontekst for å generere gode scripts. synops-orchestrate --generate-system-prompt bygger dette automatisk fra PG:

Du er en orkestreringsplanlegger for Synops.

TILGJENGELIGE VERKTØY:
- synops-transcribe: Whisper-transkribering
  Bruk: --cas-hash <hash> --model <model>
- synops-summarize: AI-oppsummering
  Bruk: --communication-id <uuid>
- synops-rss: RSS-generering
  Bruk: --collection-id <uuid>
[... hentet fra cli_tool-noder i PG]

SCRIPT-GRAMMATIKK:
  NÅR <event> [HVIS <betingelse>]
  <N>. <tool> <args...>
  VED_FEIL: <tool> <args...> | work_item <title> [--tag <tag>]
  Variabler: {event.<felt>}, {input.<felt>}

EKSEMPLER:
[... hentet fra eksisterende orchestration-noder]

Svar KUN med et gyldig orkestreringscript.

Systemprompt oppdateres automatisk når nye verktøy legges til. Fungerer med alle modeller — Claude, Llama, Mixtral, Grok.

Tre-stegs flyten

1. OPPRETTING (AI, én gang)
   Bruker beskriver → synops-ai genererer script
   → vaktmester validerer → bruker godkjenner → lagres

2. KJØRING (ingen AI, hver gang)
   Trigger → vaktmester parser script → utfører CLI-kall
   → logger resultat

3. FEILHÅNDTERING (AI, sjelden)
   VED_FEIL feiler → eskalér til synops-ai
   → foreslå fix eller opprett work_item

AI-kostnad er nesten null i drift.

Naturlig progresjon

Drømmemodus  → Bruker beskriver ønsket resultat
  ↓ AI genererer script-forslag
Script       → Vaktmester validerer, bruker godkjenner
  ↓ Kjører uten AI
Produksjon   → Deterministisk, gratis, raskt
  ↓ Hvis mønster gjentas
Kode         → Eget CLI-verktøy (synops-<verb>)

AI ved oppretting. Ingen AI ved kjøring. Script kan alltid redigeres manuelt.

5. Strukturert trigger

Triggeren er alltid strukturert — vaktmesteren evaluerer den effektivt uten LLM, uavhengig av utførelsesnivå:

{
  "trigger": {
    "event": "communication.ended",
    "conditions": {
      "has_trait": "podcast",
      "has_media": "audio"
    }
  }
}

Kjente trigger-events

Event Beskrivelse
node.created Ny node opprettet
edge.created Ny edge opprettet
communication.ended Samtale/innspilling avsluttet
node.published Node publisert (belongs_to med slot)
scheduled.due Planlagt tidspunkt nådd
manual Bruker trykker "Kjør"

Betingelser

Filtrerer triggeren ytterligere:

  • has_trait — noden/samlingen har denne traiten
  • has_media — noden har media-edge av denne typen
  • has_tag — noden har tagged-edge med denne verdien
  • node_kind — noden er av denne typen

6. Utførelse

Script-modus (nivå 1)

Trigger aktiveres
  → 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.

Feilhåndtering ved kjøring

Alle orkestreringer kjører som script (nivå 1). Ved feil:

Script kjører steg 2
  → synops-summarize returnerer exit 1
  → VED_FEIL definert? → kjør alternativ
  → Alternativ feiler også?
    → Opprett work_item med feilbeskrivelse
    → Logger i orchestration_log
    → Stopp orkestreringen

Ingen AI-eskalering ved kjøring. Feil håndteres av scriptet (VED_FEIL) eller blir work_items for manuell/AI-assistert oppfølging.

Asynkron modus: "eventually"

Brukeren kan godta at et svar eller script-generering kommer etterhvert i stedet for umiddelbart:

Bruker: "Lag en orkestrering for podcast-pipeline"
  → [Nå] [Eventually]

"Eventually":
  → Forespørselen lagres som work_item med tag "script_request"
  → Neste Claude Code-sesjon (task runner) plukker den opp
  → Genererer script med Opus (full verktøy-tilgang, fillesing)
  → Scriptet valideres og lagres
  → Bruker varsles: "Orkestreringen din er klar"

Fordeler:

  • Ingen API-kostnad — Claude Code (betalt) gjør jobben
  • Bedre kvalitet — Opus med full kontekst, ikke en lettvekts API-modell
  • Ingen hastverk — script-generering trenger ikke skje i sanntid

Dette gjelder også feilhåndtering: et feilet VED_FEIL-steg blir en work_item som Claude Code løser i neste sesjon — reparerer scriptet, legger til manglende verktøy, osv.

7. Koblinger mellom orkestreringer (kaskade)

Orkestreringer er noder med edges:

"Podcast: opptak → publisering"
  ──triggers──→ "Varsle redaksjonen om ny episode"
  ──triggers──→ "Post til sosiale medier"

Output fra én orkestrering kan trigge neste via triggers-edge. Kaskade via edges, ikke hardkodet.

Implementasjon (oppgave 24.8)

Etter vellykket pipeline-utførelse sjekker handle_orchestrate() om orkestreringen har utgående triggers-edges. For hvert gyldig mål (må være node_kind = 'orchestration') legges en ny orchestrate-jobb i køen med:

  • trigger_event: "cascade" — skiller kaskade fra primær-triggere
  • trigger_context.upstream_orchestration_id — ID til kilden
  • trigger_context.op: "CASCADE" — for betingelsesmatching
  • cascade_chain — liste med alle orchestration-IDer allerede utført

Nedstrøms script kan bruke {event.upstream_orchestration_id} for å referere til oppstrøms orkestrering.

Syklusdeteksjon

Kaskadekjeden (cascade_chain) spores som en array i jobb-payloaden. Før enqueue av hvert mål sjekkes:

  1. Dybdegrense: Maks 10 ledd i kjeden (konfigurerbart via MAX_CASCADE_DEPTH)
  2. Syklussjekk: Target-ID finnes ikke allerede i kjeden (inkludert orkestreringen som nettopp fullførte)

Blokkerte kaskader logges i orchestration_log med status = 'skipped' og tool_binary = 'cascade'. Kaskade-feil er ikke-fatale — den fullførte orkestreringen rapporteres fortsatt som suksess.

8. Brukergrensesnitt

┌─ Podcastorkestrering ⚡ ──────────────────┐
│                                           │
│  Trigger: [Innspilling avsluttet ▼]       │
│  Betingelse: [Samling har podcast-trait ▼] │
│  Modus: [Script ▼]                        │
│                                           │
│  ┌─────────────────────────────────────┐  │
│  │ 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} │  │
│  └─────────────────────────────────────┘  │
│                                           │
│  [▶ 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)                           │
└───────────────────────────────────────────┘

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

Edge Source → Target Betydning
belongs_to orchestration → collection Tilhører denne samlingen
observes orchestration → any node Overvåker denne noden for trigger-events
triggers orchestration → orchestration Kaskade-kobling
uses orchestration → cli_tool Bruker dette verktøyet
mentions orchestration → any Refererer til denne noden

observes-edge: eksplisitt kobling

Orkestreringen peker på det den observerer:

"Auto-clip URL-er" (orchestration)
  ──observes──→ #Redaksjonen (communication)
  ──observes──→ #Research (communication)

Legg til observes-edge → aktivert for den noden. 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."
  • Trigger-betingelser: Implisitt. "Overvåk alt som matcher."
  • observes overtrumfer: fjernet observes-edge betyr "ikke her", selv om betingelsene matcher. Brukeren har kontroll.

10. Drømmemodus: brukeren skriver hva de vil

Brukeren begrenses ikke til kjente verktøy. De skriver fritt — boten prøver, og mangler som oppdages blir feature requests.

Bruker skriver: "Lag en lydfil med sammendrag og send til deltakerne"

Boten sjekker:
  ✓ synops-summarize finnes → oppsummerer
  ✗ synops-tts finnes ikke → kan ikke lage lyd

Boten svarer:
  "Jeg oppsummerte møtet, men Synops har ikke tekst-til-tale
   ennå. Jeg oppretter en forespørsel?"

  → work_item i innboks:
    title: "TTS for møteoppsummeringer"
    tagged: "feature"
    source_material → orkestreringsnoden

Systemet lærer hva brukerne vil ha fra det som ikke lykkes.

11. Prinsipp: målet er alltid script

Enhver orkestrering har som mål å bli et deklarativt script. AI-nivåene (fritekst, drøm) er verktøy for å komme dit, ikke permanente driftsmoduser.

Hvis en orkestrering ikke kan uttrykkes som script, betyr det én av to ting:

  1. Verktøyet mangler. Lag et nytt CLI-verktøy som dekker det manglende steget. Da kan scriptet uttrykke det.
  2. Logikken er for vag. Stram opp instruksjonene til de er presise nok for et script. Hvis det ikke er mulig, er orkestreringen kanskje ikke moden nok for automatisering.

AI som permanent feilhåndtering i en orkestrering er et tegn på at noe er galt — enten med verktøyet eller med spesifikasjonen. Det skal fikses, ikke kompenseres.

Drømmemodus  → "Hva vil vi oppnå?"
Fritekst     → "Hvordan gjør vi det?"
Script       → "Nøyaktig dette."
  ↓
Hvis script ikke er mulig:
  → Mangler verktøy?  → Lag synops-<verb>
  → For vagt?         → Stram opp spec
  → Umulig?           → Ikke automatiser dette

12. Avgrensning

  • Orkestreringer er ikke en generell workflow-engine. 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.

13. Komponenter

Feature Rolle
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

14. Seed-orkestreringer

Fem standard-orkestreringer leveres som eksempler og utgangspunkt:

Orkestrering Trigger Hva den gjør
Podcast: opptak → publisering communication.ended + podcast + audio Transkriberer, oppsummerer, genererer RSS
Publisering: artikkel → render → RSS node.published + publishing Rendrer HTML, indeks, RSS
AI-beriking: foreslå koblinger node.created + content Foreslår emne-edges via AI
Planlagt publisering scheduled.due + publishing Rendrer og genererer RSS ved planlagt tid
Podcast: oppsummering → lyd (TTS) cascade (fra podcast-pipeline) Leser oppsummering som lyd

Podcast-pipeline → TTS er koblet med triggers-edge og demonstrerer kaskade-mønsteret. Se migrations/025_seed_orchestrations.sql.

Flerords-verb

Kompilatoren støtter flerords-verb (f.eks. "generer feed", "les opp", "foreslå koblinger"). Parseren splitter alltid ved første mellomrom, men kompilatoren prøver å sette verb + begynnelsen av objekt sammen for å matche alias. "generer feed for samlingen" matcher alias "generer feed" i synops-rss, med "for samlingen" som objekt.

15. 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