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.
This commit is contained in:
parent
98654c4a84
commit
1f21f90f76
4 changed files with 265 additions and 17 deletions
|
|
@ -573,7 +573,30 @@ Hvis script ikke er mulig:
|
|||
| Arbeidstavlen | Work items opprettes ved feil |
|
||||
| Responskvalitet | Intelligence/effort per orkestrering |
|
||||
|
||||
## 14. Bygger på
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -479,6 +479,43 @@ pub fn compile(parsed: &ParsedScript, registry: &ToolRegistry) -> CompileResult
|
|||
}
|
||||
}
|
||||
|
||||
/// Prøv å finne verktøy med flerords-verb.
|
||||
///
|
||||
/// Parseren splitter "generer feed for samlingen" til verb="generer", object="feed for samlingen".
|
||||
/// Hvis "generer" ikke matcher noe alias, prøv "generer feed" (verb + første ord av object),
|
||||
/// og returner resten av object som effektivt objekt.
|
||||
fn find_tool_with_multiword_verb<'a>(
|
||||
step: &ParsedStep,
|
||||
registry: &'a ToolRegistry,
|
||||
) -> Result<(&'a ToolDef, String), Diagnostic> {
|
||||
// 1. Prøv enkeltord-verb
|
||||
if let Some(tool) = registry.find_by_verb(&step.verb) {
|
||||
return Ok((tool, step.object.clone()));
|
||||
}
|
||||
|
||||
// 2. Prøv flerords-verb: "verb + N første ord av object"
|
||||
if !step.object.is_empty() {
|
||||
let object_words: Vec<&str> = step.object.split_whitespace().collect();
|
||||
for take in 1..=object_words.len().min(3) {
|
||||
let candidate = format!("{} {}", step.verb, object_words[..take].join(" "));
|
||||
if let Some(tool) = registry.find_by_verb(&candidate) {
|
||||
let remaining = object_words[take..].join(" ");
|
||||
return Ok((tool, remaining));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Ingen match — gi feilmelding
|
||||
Err(Diagnostic {
|
||||
line: step.line_number,
|
||||
severity: Severity::Error,
|
||||
message: format!("\"{}\" matcher ingen verktøy", step.verb),
|
||||
suggestion: registry.suggest(&step.verb),
|
||||
raw_input: step.raw.clone(),
|
||||
compiled_output: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Kompiler ett steg.
|
||||
fn compile_step(
|
||||
step: &ParsedStep,
|
||||
|
|
@ -489,25 +526,16 @@ fn compile_step(
|
|||
return compile_work_item(step);
|
||||
}
|
||||
|
||||
// Finn verktøy via verb
|
||||
let tool = registry.find_by_verb(&step.verb).ok_or_else(|| {
|
||||
let suggestion = registry.suggest(&step.verb);
|
||||
Diagnostic {
|
||||
line: step.line_number,
|
||||
severity: Severity::Error,
|
||||
message: format!("\"{}\" matcher ingen verktøy", step.verb),
|
||||
suggestion,
|
||||
raw_input: step.raw.clone(),
|
||||
compiled_output: None,
|
||||
}
|
||||
})?;
|
||||
// Finn verktøy via verb — prøv først enkeltord, deretter flerords-verb
|
||||
// (f.eks. "generer feed" der verb="generer" og object="feed for samlingen")
|
||||
let (tool, effective_object) = find_tool_with_multiword_verb(step, registry)?;
|
||||
|
||||
// Bygg argumentliste
|
||||
let mut cli_args = Vec::new();
|
||||
|
||||
// Map objektet via args_hints (f.eks. "lydfilen" → "--cas-hash {event.cas_hash}")
|
||||
if !step.object.is_empty() {
|
||||
if let Some(hint) = tool.args_hints.get(&step.object.to_lowercase()) {
|
||||
if !effective_object.is_empty() {
|
||||
if let Some(hint) = tool.args_hints.get(&effective_object.to_lowercase()) {
|
||||
// Hint kan inneholde variabel ({event.xxx}) eller flagg (--flag value)
|
||||
expand_hint(hint, &mut cli_args);
|
||||
}
|
||||
|
|
@ -924,4 +952,44 @@ ved feil: opprett oppgave "Pipeline feilet" (bug)
|
|||
assert!(parsed.global_fallback.is_some());
|
||||
assert_eq!(parsed.global_fallback.unwrap().verb, "opprett");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiword_verb() {
|
||||
// "oppdater rss-feed" er et flerords-alias. Parseren splitter til
|
||||
// verb="oppdater", object="rss-feed". compile_step skal finne alias
|
||||
// "oppdater rss-feed" via flerords-matching.
|
||||
let registry = test_registry();
|
||||
let script = "1. oppdater rss-feed\n";
|
||||
let parsed = parse(script).unwrap();
|
||||
let result = compile(&parsed, ®istry);
|
||||
// Bør kompilere OK — "oppdater" matcher direkte som alias
|
||||
assert!(!result.has_errors());
|
||||
|
||||
// Test med alias som KUN finnes som flerord
|
||||
let mut multi_registry = ToolRegistry {
|
||||
tools: vec![ToolDef {
|
||||
binary: "synops-rss".into(),
|
||||
aliases: vec!["generer feed".into()],
|
||||
description: "RSS".into(),
|
||||
args_hints: HashMap::from([(
|
||||
"for samlingen".into(),
|
||||
"--collection-id {event.collection_id}".into(),
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let script2 = "1. generer feed for samlingen\n";
|
||||
let parsed2 = parse(script2).unwrap();
|
||||
let result2 = compile(&parsed2, &multi_registry);
|
||||
assert!(
|
||||
!result2.has_errors(),
|
||||
"Flerords-verb 'generer feed' bør matche: {:?}",
|
||||
result2.diagnostics
|
||||
);
|
||||
let compiled = result2.compiled.unwrap();
|
||||
assert_eq!(compiled.steps[0].binary, "synops-rss");
|
||||
assert_eq!(
|
||||
compiled.steps[0].args,
|
||||
vec!["--collection-id", "{event.collection_id}"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
158
migrations/025_seed_orchestrations.sql
Normal file
158
migrations/025_seed_orchestrations.sql
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
-- 025_seed_orchestrations.sql
|
||||
-- Oppgave 24.9: Seed-orkestreringer for podcast-pipeline, publiseringsflyt,
|
||||
-- og AI-beriking. Skrevet i menneskelig scriptspråk som kompileres av
|
||||
-- script_compiler til tekniske CLI-kall.
|
||||
--
|
||||
-- Disse fungerer som standard-orkestreringer og som eksempler for brukere
|
||||
-- som vil lage egne. De erstatter hardkodet logikk i vaktmesteren.
|
||||
--
|
||||
-- Ref: docs/concepts/orkestrering.md
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. Podcast-pipeline: opptak → transkribering → oppsummering → RSS
|
||||
-- =============================================================================
|
||||
-- Erstatter hardkodet flyt: communication.ended → whisper_transcribe → summarize → rss
|
||||
-- Trigger: communication.ended + samling med podcast-trait + lydfil
|
||||
INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000001',
|
||||
'orchestration',
|
||||
'Podcast: opptak → publisering',
|
||||
E'NÅR innspilling avsluttet\nHVIS samling har podcast\n\n1. transkriber lydfilen (stor modell)\n ved feil: transkriber lydfilen (medium modell)\n2. oppsummer samtalen\n3. generer feed for samlingen\n\nved feil: opprett oppgave "Podcast-pipeline feilet" (bug)',
|
||||
'discoverable',
|
||||
'{
|
||||
"trigger": {
|
||||
"event": "communication.ended",
|
||||
"conditions": {
|
||||
"has_trait": "podcast",
|
||||
"has_media": "audio"
|
||||
}
|
||||
},
|
||||
"executor": "script",
|
||||
"intelligence": 2,
|
||||
"effort": 3,
|
||||
"compiled": false
|
||||
}'::jsonb,
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. Publiseringsflyt: artikkel publisert → render → RSS
|
||||
-- =============================================================================
|
||||
-- Erstatter hardkodet flyt: render_article → render_index → rss
|
||||
-- Trigger: node.published (belongs_to med slot i publiseringssamling)
|
||||
INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000002',
|
||||
'orchestration',
|
||||
'Publisering: artikkel → render → RSS',
|
||||
E'NÅR artikkel publisert\nHVIS samling har publishing\n\n1. render noden (som artikkel)\n ved feil: opprett oppgave "Rendering feilet" (bug)\n2. render noden (som indeks, i samlingen)\n3. generer feed for samlingen\n\nved feil: opprett oppgave "Publisering feilet" (bug)',
|
||||
'discoverable',
|
||||
'{
|
||||
"trigger": {
|
||||
"event": "node.published",
|
||||
"conditions": {
|
||||
"has_trait": "publishing"
|
||||
}
|
||||
},
|
||||
"executor": "script",
|
||||
"intelligence": 1,
|
||||
"effort": 2,
|
||||
"compiled": false
|
||||
}'::jsonb,
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. AI-beriking: nytt innhold → foreslå koblinger
|
||||
-- =============================================================================
|
||||
-- Erstatter hardkodet flyt: node.created → suggest_edges
|
||||
-- Trigger: node.created med node_kind content
|
||||
INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000003',
|
||||
'orchestration',
|
||||
'AI-beriking: foreslå koblinger',
|
||||
E'NÅR innhold opprettet\nHVIS node_kind er content\n\n1. foreslå koblinger for noden\n\nved feil: opprett oppgave "AI-beriking feilet" (bug)',
|
||||
'discoverable',
|
||||
'{
|
||||
"trigger": {
|
||||
"event": "node.created",
|
||||
"conditions": {
|
||||
"node_kind": "content"
|
||||
}
|
||||
},
|
||||
"executor": "script",
|
||||
"intelligence": 2,
|
||||
"effort": 1,
|
||||
"compiled": false
|
||||
}'::jsonb,
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. Planlagt publisering: tidspunkt nådd → render → RSS → varsle
|
||||
-- =============================================================================
|
||||
-- Trigger: scheduled.due — planlagt tidspunkt nådd
|
||||
INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000004',
|
||||
'orchestration',
|
||||
'Planlagt publisering: timer → render → RSS',
|
||||
E'NÅR planlagt tid nådd\nHVIS samling har publishing\n\n1. render noden (som artikkel)\n2. generer feed for samlingen\n\nved feil: opprett oppgave "Planlagt publisering feilet" (bug)',
|
||||
'discoverable',
|
||||
'{
|
||||
"trigger": {
|
||||
"event": "scheduled.due",
|
||||
"conditions": {
|
||||
"has_trait": "publishing"
|
||||
}
|
||||
},
|
||||
"executor": "script",
|
||||
"intelligence": 1,
|
||||
"effort": 2,
|
||||
"compiled": false
|
||||
}'::jsonb,
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 5. Podcast-oppsummering til lyd (TTS) — kaskademål for podcast-pipeline
|
||||
-- =============================================================================
|
||||
-- Designet som kaskademål: kobles via triggers-edge fra podcast-pipeline.
|
||||
-- Demonstrerer kaskade-mønsteret fra oppgave 24.8.
|
||||
INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000005',
|
||||
'orchestration',
|
||||
'Podcast: oppsummering → lyd (TTS)',
|
||||
E'NÅR kaskade\n\n1. les opp teksten fra noden (på norsk)\n\nved feil: opprett oppgave "TTS-generering feilet" (bug)',
|
||||
'discoverable',
|
||||
'{
|
||||
"trigger": {
|
||||
"event": "cascade",
|
||||
"conditions": {}
|
||||
},
|
||||
"executor": "script",
|
||||
"intelligence": 1,
|
||||
"effort": 2,
|
||||
"compiled": false
|
||||
}'::jsonb,
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- Kaskade-edge: podcast-pipeline → TTS-oppsummering
|
||||
-- =============================================================================
|
||||
-- Når podcast-pipeline fullføres, trigger TTS-genereringen automatisk.
|
||||
INSERT INTO edges (source_id, target_id, edge_type, metadata)
|
||||
VALUES (
|
||||
'e0000000-0ac0-4000-b000-000000000001', -- Podcast-pipeline
|
||||
'e0000000-0ac0-4000-b000-000000000005', -- TTS-oppsummering
|
||||
'triggers',
|
||||
'{"description": "Generer lydoppsummering etter podcast-prosessering"}'::jsonb
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
3
tasks.md
3
tasks.md
|
|
@ -326,8 +326,7 @@ automatisk eskalering av intelligens ved feil, kompilering av velprøvde mønstr
|
|||
- [x] 24.6 Orchestration UI: editor med tre visninger (Enkel/Teknisk/Kompilert) som tabber. Sanntids kompileringsfeil. Trigger-velger, "Test kjøring"-knapp, kjørehistorikk. Ref: `docs/concepts/orkestrering.md`.
|
||||
- [x] 24.7 AI-assistert oppretting: `synops-ai` med auto-generert systemprompt (fra cli_tool-noder) foreslår script fra fritekst-beskrivelse. Vaktmesteren validerer. Eventually-modus: lagre som work_item for Claude Code.
|
||||
- [x] 24.8 Kaskade: `triggers`-edge mellom orkestreringer. Output fra én trigger neste. Syklusdeteksjon for å unngå uendelige loops.
|
||||
- [~] 24.9 Seed-orkestreringer: opprett standard-orkestreringer for podcast-pipeline, publiseringsflyt, og AI-beriking basert på eksisterende hardkodet logikk i vaktmesteren. Skrives i menneskelig scriptspråk.
|
||||
> Påbegynt: 2026-03-18T18:00
|
||||
- [x] 24.9 Seed-orkestreringer: opprett standard-orkestreringer for podcast-pipeline, publiseringsflyt, og AI-beriking basert på eksisterende hardkodet logikk i vaktmesteren. Skrives i menneskelig scriptspråk.
|
||||
|
||||
## Fase 25: Web Clipper — `synops-clip`
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue