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.
20 KiB
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 traitenhas_media— noden har media-edge av denne typenhas_tag— noden har tagged-edge med denne verdiennode_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-triggeretrigger_context.upstream_orchestration_id— ID til kildentrigger_context.op: "CASCADE"— for betingelsesmatchingcascade_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:
- Dybdegrense: Maks 10 ledd i kjeden (konfigurerbart via
MAX_CASCADE_DEPTH) - 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."
observesovertrumfer: fjernetobserves-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:
- Verktøyet mangler. Lag et nytt CLI-verktøy som dekker det manglende steget. Da kan scriptet uttrykke det.
- 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 byggeklosserdocs/retninger/interaksjonsmodell.md— drag-and-drop for observes-edgedocs/concepts/arbeidstavlen.md— @bot, work items ved feildocs/infra/robusthet.md— function calling, fallbackdocs/features/responskvalitet.md— intelligence/effort-nivåer