This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Sunday, 11 January 2026
Jag byggde StyloFlow eftersom jag fortsatte skriva samma mönster om och om igen: komponenter som reagerar på vad som hände innan, släpper ut självförtroende poäng, och ibland måste eskalera till mer dyrare analyser. De existerande arbetsflödemotorerna ville att jag skulle tänka i termer av DAG eller statmaskiner. Jag ville tänka i signaler.
NOTA: StyloFlow är inte än en färdig produkt; eftersom jag bygger lucidRAG och StyloBot IM SK2 jag lägger till förlorade egenskaper och polerar API: t på både StyloBllow och efemeral . DetMska4 är fortfarande i aktiv utvecklingMske5 men du kan pröva det och ge feedbackMsko6 Jag kommer att uppdatera det här senare Msek7 SignalSink-grejerna till exempel kommer att ändras för efemerala vMsku8 till att vara Mska9 bara läsbart')
StyloFlow är en signaldriven orkestrationsbibliotek som matchar hur jag tänker om AI-rör: komponenter deklarerar vad de producerar och vad de behöver
Det här är infrastrukturen som ger energi till klarRAG - ett graf RAG-verktyg som kombinerar DocSummarizer (dokument DataSummarizer (strukturerade data ), och ImageSummarizer (, bilder, ), till en förenade fråga, -, en upphöjningssystem med kunskapsgrafvisualisering. Stylobot ( ett avancerat robotskyddssystem ) och implementerar Reduzerad RAG mönster.

källa: GitHub - StyloFlow
StyloFlow är en fungerande prototyp APIn och formen kommer att utvecklas när jag bygger lucidRAG och Stylobot, men de här beskrivna sekvenserna och mönsterna är punktsignaler : som första signal, classfakter, , självförtroende,- driven branching, , och eskalation som ett strukturellt mönst,
Detta är ett nytt DSL- eller arbetsflödesspråk. avrättelsesemantik byggt runt signalern, ,, självförtroende, ,, och begränsad skalning. ., Idag kör den i en process med begränsad samspel, M SK4, imorgon delar den ut spår mellan maskiner samtidigt som signalerna är den stabila gränsen.
Här är vad de flesta arbetsflödesmotorer ser ut som
// ❌ Traditional: Hardcoded dependencies
public async Task ProcessDocumentAsync(string path)
{
var text = await ExtractTextAsync(path);
var chunks = await ChunkTextAsync(text);
var embeddings = await GenerateEmbeddingsAsync(chunks);
var entities = await ExtractEntitiesAsync(chunks);
await StoreEverythingAsync(embeddings, entities);
}
Detta fungerar tills:
Du får antingen:
StyloFlow bygger på mestlucid.ephemeral - en bibliotek för begränsad , spårbar async exécution
Vinnig sammanfattning av vad ephemeral innebär:
// Bounded concurrent processing with full visibility
var coordinator = new EphemeralWorkCoordinator<DocumentJob>(
async (job, operation, ct) => {
await ProcessAsync(job, ct);
operation.Signal("document.processed");
},
new EphemeralOptions { MaxConcurrency = 4 });
// Enqueue work
await coordinator.EnqueueAsync(new DocumentJob(filePath));
// Full observability
Console.WriteLine($"Active: {coordinator.ActiveCount}");
Console.WriteLine($"Completed: {coordinator.TotalCompleted}");
Nyckliga fördelar med ephemeral:
För mer information Ogen och Don't Bort glöm.
Detta orkesteringsmodell förlänger ephemeral med:
Här är nyckeln till den arkitektoniska förändringen.
graph TD
subgraph Traditional["❌ Traditional: Hardcoded"]
T1[Component A] -->|calls| T2[Component B]
T2 -->|calls| T3[Component C]
T3 -->|calls| T4[Component D]
end
subgraph StyloFlow["✅ StyloFlow: Signal-Driven"]
S1[Component A]
S2[Component B]
S3[Component C]
S4[Component D]
SS[Signal Sink]
S1 -.emits.-> SS
S2 -.emits.-> SS
S3 -.emits.-> SS
SS -.triggers.-> S2
SS -.triggers.-> S3
SS -.triggers.-> S4
end
style T1 stroke:#ff6b6b
style T2 stroke:#ff6b6b
style T3 stroke:#ff6b6b
style T4 stroke:#ff6b6b
style S1 stroke:#51cf66
style S2 stroke:#51cf66
style S3 stroke:#51cf66
style S4 stroke:#51cf66
style SS stroke:#339af0
Komponenter ropar aldrig på varandra . De släpper ut signaler och reagerar på signalern.
Signaler är fakta om vad som hände, inte kommandor eller händelser . De ' är förändringslösa M SK3 är nummertagna i tid, , och bär på självförtroende värden MSC5 Varje atom äger sina signaler Mska6 de Mske7 är externt förändringsiva M Ska8 Inget annat kan modifiera den listan
public record Signal
{
public required string Key { get; init; } // "document.chunked"
public object? Value { get; init; } // Optional payload
public double Confidence { get; init; } = 1.0; // 0.0 to 1.0
public required string Source { get; init; } // Which component
public DateTime Timestamp { get; init; }
public Dictionary<string, object>? Metadata { get; init; }
}
Kritisk arkitektonisk punkt: SignalSink är en uthärdlig historisk syn på saker och ting
Signalsink förmedlar en sökbar bild över alla operationer på alla koordinatorer som delar den. signaler över hela koordinatorns livscykel | - | när en operation flyttar bort från sin koordinator
// Create a shared signal sink (no parameters, signals persist)
var sink = new SignalSink();
// Coordinators manage operation lifetime, NOT signal lifetime
var coordinator = new EphemeralWorkCoordinator<string>(
ProcessAsync,
new EphemeralOptions
{
MaxConcurrency = 8,
MaxTrackedOperations = 100, // Operations evict after this
MaxOperationLifetime = TimeSpan.FromMinutes(5), // Or after this time
Signals = sink // Share the persistent view
});
// Operations emit via their emitter
public async Task ProcessAsync(string docId, SignalEmitter emitter, CancellationToken ct)
{
// Store actual data externally (cache, database, blob storage)
await cache.SetAsync($"doc-{docId}", documentData);
// Signal carries a REFERENCE, not the data
emitter.Emit("document.chunked", key: docId); // Key references external data
}
// SignalSink is readonly - it cannot alter signals
// Signals persist until their operation evicts from the coordinator
SignalSink ger två koordineringsmönster:
1. Push
// Subscribe to the sink for push notifications
sink.Subscribe(signal => {
if (signal.Is("document.chunked"))
{
// React immediately - signal includes OperationId
Console.WriteLine($"Op {signal.OperationId} chunked doc at {signal.Timestamp}");
}
});
// Returns IDisposable for cleanup
using var subscription = sink.Subscribe(HandleSignal);
2. Gå tillväga-baserade Fråg
// Get all signals for a specific operation
var opSignals = sink.GetOpSignals(operationId);
// Detect if any operation has emitted a signal
if (sink.Detect("embeddings.generated"))
{
// At least one operation has generated embeddings
}
// Sense all signals matching a condition
var recentErrors = sink.Sense(s =>
s.Signal.StartsWith("error.") &&
s.Timestamp > DateTimeOffset.UtcNow.AddMinutes(-5)
);
// Get operation summary from its signal history
var summary = sink.GetOp(operationId);
Console.WriteLine($"Operation ran for {summary?.Duration}");
Varför är detta viktigt:
Nyckeldesignprincipen: Speichera stora data (documenter, bilderM SK2 vektorer ) i kassor eller databaserMSC4 Signaler bär bara referensramar som "cache://doc-123" eller operativa nyckeln.
Till exempel koordinering:
// Operation emits signal via ISignalEmitter interface
public async Task ProcessAsync(Item item, ISignalEmitter emitter, CancellationToken ct)
{
// Emit to the sink
emitter.Emit("processing.started");
await DoWorkAsync(item, ct);
emitter.Emit("processing.completed");
}
// Wave checks if it should run by querying sink
public bool ShouldRun(string path, AnalysisContext ctx)
{
// Pull pattern: query the sink via context
return ctx.Detect("document.chunked");
}
// UI subscribes to sink for reactive updates
sink.Subscribe(signal => {
if (signal.Signal.StartsWith("document."))
{
// Push pattern: react immediately
UpdateProgressUI(signal);
}
});
Escalation sker på två nivåer
// Pattern 1: Intra-coordinator escalation (wave checks signals)
public bool ShouldRun(string path, AnalysisContext ctx)
{
var quality = ctx.GetSignal("quality.score");
return quality?.Confidence < 0.7; // Only run if quality is low
}
// Pattern 2: Inter-coordinator escalation (atom routes to another coordinator)
// Option A: Explicit escalation signal
typed.Raise("escalate.to.expensive", payload, key: "doc-123");
// Option B: EscalatorAtom examines signals and decides
new EscalatorAtomOptions<T> {
ShouldEscalate = evt => evt.Payload.Confidence < 0.7
}
Flera koordinatorer funkar oberoende av varandra . EscalatorAtom tittar på signaler från en koordinator och förflyttar arbetet till en annan när det behövs.
För teorin bakom detta, ,, se Begränsat Fuzzy Kontexttdragning.
Kritiskt Signaler är koordineringshändelser , inte dataöverföring. stora data | ( | dokument | , | bilder |, | inbäddar | ) | borde finnas i ett externt lager
// ❌ BAD: Carrying data in signals (memory pressure, boxing)
var imageBytes = await ProcessImageAsync(input);
emitter.Emit("image.processed", metadata: new { Data = imageBytes });
// ✅ GOOD: Store externally, signal the reference
var imageBytes = await ProcessImageAsync(input);
var cacheKey = $"processed/{docId}";
await cache.SetAsync(cacheKey, imageBytes);
emitter.Emit("image.processed", key: cacheKey);
// Later: Retrieve when needed
if (sink.Detect("image.processed"))
{
var signals = sink.GetOpSignals(operationId);
var imageKey = signals.FirstOrDefault(s => s.Signal == "image.processed")?.Key;
if (imageKey != null)
{
var bytes = await cache.GetAsync<byte[]>(imageKey);
}
}
De bästa metoderna:
"cache://key", "blob://container/file", "db://table/id"Manifest deklarerar kontrakt. (, vad får mig att göra det , ",, vad jag utsläpper, ,, vad det kostar" separat från implementering. Den här separationen existerar så att man kan förstå arbetsflödet utan att läsa koden , och ändra exécutionsorten utan att kompilera
name: BotDetector
priority: 10 # Lower runs first
enabled: true
# What kind of component is this?
taxonomy:
kind: analyzer # sensor|analyzer|proposer|gatekeeper
determinism: probabilistic
persistence: ephemeral
# When should this run?
triggers:
requires:
- signal: http.request.received
condition: exists
# What does it produce?
emits:
on_complete:
- key: bot.detected
confidence_range: [0.0, 1.0]
conditional:
- key: bot.escalation.needed
when: confidence < 0.7
# Resource limits
lane:
name: fast # fast|normal|slow|llm
max_concurrency: 8
budget:
max_duration: 100ms
# Configuration values
defaults:
confidence:
bot_detected: 0.6
timing:
timeout_ms: 100
Möjligheter:
Medan du kan skriva YAML-manifester av hand., StyloFlow innehåller en visuell arbetsflödesbyggare som låter dig designa signal.

Interfejsen erbjuder:
Detta gör det enkelt att experimentera med olika arbetsflödes former utan att skriva YAML på hand.
Ett våg är en komponerande analyssstadium. Den här gränssnittet existerar för att göra " skulle vi kunna köra ?" som ett första beslut, inte ett implementeringsdetail begravt i villkorlig logik .
public interface IContentAnalysisWave
{
string Name { get; }
int Priority { get; } // Higher runs first
bool Enabled { get; set; }
// Quick filter - avoid expensive work
bool ShouldRun(string contentPath, AnalysisContext context);
// Do the analysis
Task<IEnumerable<Signal>> AnalyzeAsync(
string contentPath,
AnalysisContext context,
CancellationToken ct);
}
Enkel våg exempel:
public class FileTypeWave : IContentAnalysisWave
{
public string Name => "FileType";
public int Priority => 100;
public bool Enabled { get; set; } = true;
public bool ShouldRun(string path, AnalysisContext ctx)
{
// Skip if we already know the type
return ctx.GetSignal("file.type") == null;
}
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string path,
AnalysisContext ctx,
CancellationToken ct)
{
var extension = Path.GetExtension(path);
var mimeType = GetMimeType(extension);
return new[]
{
new Signal
{
Key = "file.type",
Value = mimeType,
Confidence = 1.0,
Source = Name
}
};
}
}
Koordination av vågor
Den WaveCoordinator kör vågor i prioritära ordning:
var coordinator = new WaveCoordinator(waves, profile);
var context = new AnalysisContext();
var results = await coordinator.ExecuteAsync(filePath, context, ct);
// All signals from all waves
foreach (var signal in context.GetAllSignals())
{
Console.WriteLine($"{signal.Key}: {signal.Value}");
}
Konkurenserande vägar
Rör vågor på vägar med olika samtida gränser:
| Gadan | syfte | Konkurens | |||
|---|---|---|---|---|---|
fast |
Vinniga kontroller | IP-uppsök | , | File type | |
normal |
Standardbearbetande | Parsering | |||
io |
IM SK1O bunden (filer läser , API-sändningar) M | 32 | |||
llm |
dyra LLM-sändningar |
Detta förhindrar dyra operationer från att blockera billiga
Här är den kompletta bilden.
graph TB
subgraph Input["Input Layer"]
REQ[HTTP Request]
FILE[File Upload]
JOB[Background Job]
end
subgraph Ephemeral["Ephemeral Layer"]
COORD[Work Coordinator]
OPS[Operations<br/>own signals]
SINK[SignalSink<br/>read-only view]
end
subgraph StyloFlow["StyloFlow Layer"]
MAN[Manifests]
WAVE[Wave Coordinator]
ATOMS[Atoms<br/>own signals]
end
subgraph Execution["Execution"]
FAST[Fast Lane]
NORM[Normal Lane]
LLM[LLM Lane]
end
subgraph Output["Output"]
RES[Results]
ESCAL[Escalation]
STORE[Persistence]
end
REQ --> COORD
FILE --> COORD
JOB --> COORD
COORD --> OPS
SINK -.queries.-> OPS
WAVE -.reads.-> SINK
MAN -.configures.-> WAVE
WAVE --> ATOMS
ATOMS --> FAST
ATOMS --> NORM
ATOMS --> LLM
SINK -.queries.-> FAST
SINK -.queries.-> NORM
SINK -.queries.-> LLM
SINK -.read for.-> RES
SINK -.read for.-> ESCAL
SINK -.read for.-> STORE
style COORD stroke:#339af0
style SINK stroke:#339af0
style WAVE stroke:#51cf66
style ATOMS stroke:#51cf66
Flow:
Eigenschaftsmodell: Varje operation/ ägs sina signaler, atomen . SignalSink ger en läsning, - bara syn över alla operationer, . Signaler kan upphöjas, | ( | kopieras, M SK5 | eller ekaderas, ♫ ( | bevaras när utskjutts, ♪ ♪ ), | men listan som ägs är externt oänderbar, ♪
Den nuvarande Ausführungsmodellen: Enkela operationer med LRU-utskjutning
Framstående Ausführungsmodell: Distribuerade banor över maskiner, SignalSink frågar fjärran operationer , atomer utförs på olika värden. Signaler förblir den stabila gränsen -, de , ', är redan serialiserade, ,, stängstimmade och självbevarade. -, består av ., ägandes modellen ändrar inte på ' eller .."
Implementeringen i processen validerar semantiken.
Låt oss se hur lucidRAG använder StyloFlow:
Etappe 1: Initiell upptäckt
public class FileTypeDetectorWave : IContentAnalysisWave
{
public int Priority => 100; // Run first
public async Task<IEnumerable<Signal>> AnalyzeAsync(...)
{
var extension = Path.GetExtension(path);
return new[]
{
new Signal
{
Key = "file.extension",
Value = extension,
Source = "FileTypeDetector"
}
};
}
}
Etappen 2: Förstärkning ( aktiverad av filen.upplängningM SK3
// In manifest:
// triggers:
// requires:
// - signal: file.extension
// condition: in
// value: [".pdf", ".docx", ".md"]
public class ChunkingWave : ConfiguredComponentBase, IContentAnalysisWave
{
public int Priority => 80;
public async Task<IEnumerable<Signal>> AnalyzeAsync(...)
{
var chunks = await ChunkDocumentAsync(path);
ctx.SetCached("chunks", chunks); // Share with other waves
return new[]
{
new Signal
{
Key = "document.chunked",
Value = chunks.Count,
Source = Name
}
};
}
}
Etappe 3: Embedding ( trigerade av dokumentet. instängdaM SK3
public class EmbeddingWave : ConfiguredComponentBase, IContentAnalysisWave
{
public int Priority => 60;
public bool ShouldRun(string path, AnalysisContext ctx)
{
// Only run if chunking succeeded
return ctx.GetSignal("document.chunked") != null;
}
public async Task<IEnumerable<Signal>> AnalyzeAsync(...)
{
var chunks = ctx.GetCached<List<Chunk>>("chunks");
var embeddings = await GenerateEmbeddingsAsync(chunks);
ctx.SetCached("embeddings", embeddings);
return new[]
{
new Signal
{
Key = "embeddings.generated",
Value = embeddings.Count,
Source = Name
}
};
}
}
Etappe 4: Enhetens extraktion ♫ ( ♫ likvärdig med inbäddning ♫
public class EntityExtractionWave : ConfiguredComponentBase, IContentAnalysisWave
{
public int Priority => 60; // Same as embedding - runs in parallel
public async Task<IEnumerable<Signal>> AnalyzeAsync(...)
{
var chunks = ctx.GetCached<List<Chunk>>("chunks");
// Use deterministic IDF scoring, not LLM per chunk
// (See Reduced RAG pattern)
var entities = await ExtractEntitiesAsync(chunks);
return new[]
{
new Signal
{
Key = "entities.extracted",
Value = entities.Count,
Confidence = CalculateConfidence(entities),
Source = Name
}
};
}
}
Etappe 5: Jaktkontroll
public class QualityCheckWave : ConfiguredComponentBase, IContentAnalysisWave
{
public int Priority => 40; // After embedding + entities
public async Task<IEnumerable<Signal>> AnalyzeAsync(...)
{
var embeddingSignal = ctx.GetSignal("embeddings.generated");
var entitySignal = ctx.GetSignal("entities.extracted");
var embeddingCount = (int)embeddingSignal.Value;
var entityConfidence = entitySignal.Confidence;
var quality = CalculateQuality(embeddingCount, entityConfidence);
var signals = new List<Signal>
{
new Signal
{
Key = "quality.score",
Value = quality,
Source = Name
}
};
// Trigger escalation if quality is poor
if (quality < GetParam<double>("quality_threshold", 0.7))
{
signals.Add(new Signal
{
Key = "escalation.needed",
Value = "low_quality_document",
Source = Name
});
}
return signals;
}
}
Möjligheterna med den här metoden
Det här är Reduzerad RAG mönster i aktion: deterministisk extraktion framifrån , LLMs bara för syntesten.
Stylobot är ett avancerat Bot-detektorsystem som använder StyloFlow för multi- -detektorn.
Pattern 1: Ventilator
Ett signal utlöser flera vågor:
graph LR
S1[document.uploaded] --> W1[ChunkingWave]
S1 --> W2[MetadataWave]
S1 --> W3[LanguageDetectionWave]
W1 -.signal.-> S2[document.chunked]
W2 -.signal.-> S3[metadata.extracted]
W3 -.signal.-> S4[language.detected]
style S1 stroke:#339af0
style S2 stroke:#339af0
style S3 stroke:#339af0
style S4 stroke:#339af0
style W1 stroke:#51cf66
style W2 stroke:#51cf66
style W3 stroke:#51cf66
Pattern 2: Sekvationell beroende
vågor väntar på tidigare signaler:
graph LR
W1[ExtractWave] -.signal.-> S1[text.extracted]
S1 --> W2[ChunkWave]
W2 -.signal.-> S2[text.chunked]
S2 --> W3[EmbedWave]
W3 -.signal.-> S3[embeddings.generated]
style S1 stroke:#339af0
style S2 stroke:#339af0
style S3 stroke:#339af0
style W1 stroke:#51cf66
style W2 stroke:#51cf66
style W3 stroke:#51cf66
Pattern 3: villkorlig böjning
Olika vågor körs baserat på signaler:
graph TD
W1[DetectorWave] -.signal.-> S1{confidence}
S1 -->|< 0.4| W2[RejectWave]
S1 -->|0.4-0.7| W3[EscalateWave]
S1 -->|> 0.7| W4[AcceptWave]
W2 -.signal.-> S2[rejected]
W3 -.signal.-> S3[escalated]
W4 -.signal.-> S4[accepted]
style S1 stroke:#ffd43b
style S2 stroke:#ff6b6b
style S3 stroke:#ff922b
style S4 stroke:#51cf66
style W1 stroke:#339af0
style W2 stroke:#ff6b6b
style W3 stroke:#ff922b
style W4 stroke:#51cf66
Pattern 4: Aggregation
Flera signaler aktiverar en våg:
graph LR
W1[Wave A] -.signal.-> S1[a.complete]
W2[Wave B] -.signal.-> S2[b.complete]
W3[Wave C] -.signal.-> S3[c.complete]
S1 --> T{All Ready?}
S2 --> T
S3 --> T
T -->|Yes| W4[AggregatorWave]
W4 -.signal.-> S4[aggregation.complete]
style S1 stroke:#339af0
style S2 stroke:#339af0
style S3 stroke:#339af0
style S4 stroke:#51cf66
style T stroke:#ffd43b
style W4 stroke:#51cf66
StyloFlow stödjer eskalation på två nivåer:
Ett exempel från lucidRAG: Först körs snabb enhetens extraktion. Om kvalitet M SK2 0.7, EscalatorAtom förflyttar dokumentet till en dyr LLM förnyelsekoordinatörMSC4 Detta sparar 20x på kostnaderna genom att undvika dyra LLM-sändningar efter högkvalitetsextraktionerMska7
Det här är inte en snabb guide, det är det minsta exemplet som visar hur modellen passar ihop.
Installation:
dotnet add package StyloFlow.Complete
Conceptual entry point:
// 1. Define a wave
public class MyAnalysisWave : IContentAnalysisWave
{
public string Name => "MyAnalysis";
public int Priority => 50;
public bool Enabled { get; set; } = true;
public bool ShouldRun(string path, AnalysisContext ctx) => true;
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string path,
AnalysisContext ctx,
CancellationToken ct)
{
// Your analysis logic here
var result = await AnalyzeAsync(path);
return new[]
{
new Signal
{
Key = "my.signal",
Value = result,
Confidence = 1.0,
Source = Name
}
};
}
}
// 2. Register waves
var waves = new List<IContentAnalysisWave>
{
new MyAnalysisWave(),
new AnotherWave(),
};
// 3. Create coordinator
var coordinator = new WaveCoordinator(
waves,
CoordinatorProfile.Default);
// 4. Execute
var context = new AnalysisContext();
var results = await coordinator.ExecuteAsync(filePath, context);
// 5. Read signals
foreach (var signal in context.GetAllSignals())
{
Console.WriteLine($"{signal.Key}: {signal.Value} ({signal.Confidence})");
}
Med manifester:
// Load manifests from directory
var loader = new FileSystemManifestLoader("./manifests");
var manifests = await loader.LoadAllAsync();
// Build waves from manifests
var waves = manifests
.Where(m => m.Enabled)
.OrderBy(m => m.Priority)
.Select(m => WaveFactory.Create(m))
.ToList();
var coordinator = new WaveCoordinator(waves, profile);
För kompletta exempel, se StyloFlow GitHubs arkiv.
En av StyloFlow's viktigaste egenskaper är arbetsflödet att upptäcka - du kan förstå hela pipelinen bara genom att läsa manifesten . Ingen koddykning krävs
Här är den faktiska manifestkatalogstrukturen för lucidRAG
manifests/
├── 01-file-type-detector.yaml
├── 02-chunking.yaml
├── 03-embedding.yaml
├── 04-entity-extraction.yaml
├── 05-quality-check.yaml
└── 06-escalation.yaml
01-lêer -typ-detektorMska3yamlM Ska4
name: FileTypeDetector
priority: 100
enabled: true
description: Detects file type from extension
taxonomy:
kind: sensor
determinism: deterministic
persistence: ephemeral
triggers:
requires:
- signal: document.uploaded
condition: exists
emits:
on_start:
- file.detection.started
on_complete:
- key: file.extension
type: string
confidence_range: [1.0, 1.0]
- key: file.mime_type
type: string
confidence_range: [1.0, 1.0]
lane:
name: fast
max_concurrency: 16
budget:
max_duration: 10ms
02-chunking
name: ChunkingWave
priority: 80
enabled: true
description: Splits documents into semantic chunks
taxonomy:
kind: extractor
determinism: deterministic
persistence: ephemeral
input:
accepts:
- document.pdf
- document.docx
- document.markdown
required_signals:
- file.extension
triggers:
requires:
- signal: file.extension
condition: in
value: [".pdf", ".docx", ".md", ".txt"]
emits:
on_complete:
- key: document.chunked
type: integer
confidence_range: [1.0, 1.0]
- key: chunks.cached
type: boolean
lane:
name: normal
max_concurrency: 8
budget:
max_duration: 30s
defaults:
chunking:
max_chunk_size: 512
overlap: 50
respect_boundaries: true
03-
name: EmbeddingWave
priority: 60
ires:
- signal: file.extension
condition: in
value: [".pdf", ".docx", ".md", ".txt"]
emits:
on_complete:
- key: document.chunked
type: integer
confidence_range: [1.0, 1.0]
- key: chunks.cached
type: boolean
lane:
name: normal
max_concurrency: 8
budget:
max_duration: 30s
defaults:
chunking:
max_chunk_size: 512
overlap: 50
respect_boundaries: true
03-
name: EmbeddingWave
priority: 60
enabled: true
description: Generates ONNX embeddings for chunks
taxonomy:
kind: embedder
determinism: deterministic
persistence: cached
input:
required_signals:
- document.chunked
- chunks.cached
triggers:
requires:
- signal: document.chunked
condition: ">"
value: 0
emits:
on_complete:
- key: embeddings.generated
type: integer
confidence_range: [1.0, 1.0]
lane:
name: normal
max_concurrency: 4
budget:
max_duration: 2m
max_cost: 0.0 # Local ONNX model
defaults:
embedding:
model: all-MiniLM-L6-v2
batch_size: 32
04-
name: EntityExtractionWave
priority: 60 # Same as embedding - runs in parallel
enabled: true
description: Extracts entities using IDF scoring
taxonomy:
kind: extractor
determinism: deterministic
persistence: persisted
input:
required_signals:
- document.chunked
triggers:
requires:
- signal: document.chunked
condition: ">"
value: 0
emits:
on_complete:
- key: entities.extracted
type: integer
confidence_range: [0.0, 1.0] # Confidence varies
lane:
name: normal
max_concurrency: 8
budget:
max_duration: 1m
defaults:
entity:
min_idf_score: 2.5
min_frequency: 2
max_entities: 100
05-
name: QualityCheckWave
priority: 40
enabled: true
description: Validates extraction quality
taxonomy:
kind: gatekeeper
determinism: deterministic
persistence: ephemeral
input:
required_signals:
- embeddings.generated
- entities.extracted
triggers:
requires:
- signal: embeddings.generated
condition: ">"
value: 0
- signal: entities.extracted
condition: exists
emits:
on_complete:
- key: quality.score
type: double
confidence_range: [0.0, 1.0]
conditional:
- key: escalation.needed
when: quality.score < 0.7
lane:
name: fast
max_concurrency: 16
defaults:
quality:
min_embeddings: 5
min_entity_confidence: 0.5
threshold: 0.7
06-
name: EscalationWave
priority: 20
enabled: true
description: Improves low-quality extractions using LLM
taxonomy:
kind: proposer
determinism: probabilistic
persistence: persisted
input:
required_signals:
- escalation.needed
triggers:
requires:
- signal: escalation.needed
condition: exists
skip_when:
- signal: budget.exhausted
emits:
on_complete:
- key: escalation.complete
type: boolean
- key: entities.improved
type: integer
confidence_range: [0.7, 1.0]
lane:
name: llm
max_concurrency: 2 # Expensive
budget:
max_duration: 30s
max_tokens: 4000
max_cost: 0.05
defaults:
llm:
model: gpt-4o-mini
temperature: 0.1
prompt_template: entity_extraction
När du tittar på de här plierna, så vet du omedelbart:
Inget kodexlätande krävs. arbetsflödet är självgående
Medan de tidigare exempelna visar fullt deklarerande YAML, vågor kan också vara kod-baserade atomer refererat i manifester:
name: CustomAnalyzer
priority: 50
enabled: true
description: Custom analysis logic
# Reference a code-based atom implementation
implementation:
assembly: MyProject.Analyzers
type: MyProject.Analyzers.CustomAnalyzerWave
method: AnalyzeAsync
# The manifest still declares the contract
taxonomy:
kind: analyzer
determinism: probabilistic
triggers:
requires:
- signal: data.ready
emits:
on_complete:
- key: analysis.complete
confidence_range: [0.0, 1.0]
lane:
name: normal
max_concurrency: 4
# Configuration values passed to the atom
defaults:
threshold: 0.75
max_iterations: 10
Implementering av C#
public class CustomAnalyzerWave : ConfiguredComponentBase, IContentAnalysisWave
{
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string path,
AnalysisContext ctx,
CancellationToken ct)
{
// Access manifest config
var threshold = GetParam<double>("threshold", 0.75);
var maxIterations = GetParam<int>("max_iterations", 10);
// Custom logic here
var result = await PerformComplexAnalysis(path, threshold, maxIterations);
return new[]
{
new Signal
{
Key = "analysis.complete",
Value = result.Score,
Confidence = result.Confidence,
Source = Name
}
};
}
}
Möjligheter:
Den här hybrida tillvägagångssätten ger dig deklarativt discovery av arbetsflödet medan du håller komplex logik i underhållbar C#.
Den manifesterade strukturen gör det obetydligt att skapa visualiseringar:
graph TD
DOC[document.uploaded] --> FT[FileTypeDetector<br/>Priority: 100<br/>Lane: fast]
FT --> EXT[file.extension]
EXT --> CH[ChunkingWave<br/>Priority: 80<br/>Lane: normal]
CH --> CHUNKED[document.chunked]
CHUNKED --> EMB[EmbeddingWave<br/>Priority: 60<br/>Lane: normal]
CHUNKED --> ENT[EntityExtractionWave<br/>Priority: 60<br/>Lane: normal]
EMB --> EMBGEN[embeddings.generated]
ENT --> ENTEX[entities.extracted]
EMBGEN --> QC[QualityCheckWave<br/>Priority: 40<br/>Lane: fast]
ENTEX --> QC
QC --> QSCORE[quality.score]
QC -.conditional.-> ESC_NEED[escalation.needed]
ESC_NEED -.-> ESC[EscalationWave<br/>Priority: 20<br/>Lane: llm]
ESC --> ESC_DONE[escalation.complete]
style DOC stroke:#339af0
style FT stroke:#51cf66
style CH stroke:#51cf66
style EMB stroke:#51cf66
style ENT stroke:#51cf66
style QC stroke:#ffd43b
style ESC stroke:#ff922b
Det här diagrammet skapades programmerat från YAML-manifesterna - ingen handtekening.
En oväntad egenskap av StyloFlow är att den skapar ett system LLMs kan resonera på ett säkert sätt.
De flesta försök att använda LLMs för debugging eller orkestering misslyckas eftersom systemet de droppat in i är osynligt.
StyloFlow gör motsatsen explicita, oföränderliga fakta om vad som hände
Det gör att kod LLMs verkligen är användbara, inte som actorer, utan som analytiker.
I StyloFlow
Istället, ",", får man ett Signalsink-bilden och ställde frågor som:
Ett exempel på inmatning till en kod LLM:
{
"operation": "doc-123",
"signals": [
{ "key": "document.chunked", "value": 12, "confidence": 1.0, "source": "ChunkingWave" },
{ "key": "entities.extracted", "value": 4, "confidence": 0.42, "source": "EntityWave" },
{ "key": "quality.score", "value": 0.39, "source": "QualityCheckWave" },
{ "key": "escalation.needed", "source": "QualityCheckWave" }
]
}
Det är inte en logström tänkande substans.
En kod LLM kan nu:
Allt utan att bli tillit till göra vad som helst.
Den traditionella "LLM-debuggen " försöker spela om världen
"" Här är koden och några loger "".
StyloFlow-debugging är enklare:
" Här är det exakta tillståndet som systemet observerat
Eftersom signaler är oförändliga och ägande,
LLMs skäl till fakta du redan litar på.
Detta fungerar bara på grund av strikta gränser:
Det finns ingen återkopplingsloop där LLM " avfärdar " nästa steg | | . | Maximmundo | МSK3 | föreslår förklaringar eller konfigurationsförstånd som kan tillämpas senare på en mänsklig ♫ ( ♫ eller deterministisk policy ♫
Att asymmetry är avsiktligt.
När signaler, ,, självförtroende och resultat är uppenbara, kan senare:
Inget av det kräver att en LLM kör systemet.
LLM blir en diagnostisk lins, inte en kontrollplattaM SK1
StyloFlow gör inte bara att sannolikhetssystem är säkra
Det gör dem läsbar — till människor , till tester M SK2 och till LLMs ♫ — ♫ utan att ge upp kontroll
Det är skillnaden mellan
Bara en av dessa skalor
1. Deklarerande sammansättning
Komponenter deklarerar sina kontrakt. ( triggar, , signaler, M SK2 budget, ),, inte deras beroenden. ♫ . ♫ Systemet räknar ut vad som utförs, ♫ МSK5 ♫ Det här är ingen egenskap, ♪ ' ♫ det är - ♫ ♫ vad som händer när man först skapar signaler,
2. Observerbart standardmässigt
Varje handling är en signal.
3. Adaptiv utführung
Tillförtroende poäng driver branchning utan explicit vägledningslogik. Springa över dyra steg när det är onötigt , upphöja när du inte är säker M SK2 avbryta tidigt på hög nivå - tillförtrode misslyckanden
4. Testbarhet utan att mocka ramverk
Mocksignaler, inte komponenter :
var context = new AnalysisContext();
context.AddSignal(new Signal
{
Key = "document.chunked",
Value = 10,
Confidence = 1.0,
Source = "Test"
});
var wave = new EmbeddingWave();
var results = await wave.AnalyzeAsync(path, context, ct);
Assert.Single(results);
Assert.Equal("embeddings.generated", results.First().Key);
5. Tillväxt komplexitet
Börja enkelt:
var coordinator = new EphemeralWorkCoordinator<Job>(ProcessAsync);
Lägg till signaler när det behövs:
new EphemeralOptions { Signals = signalSink }
Lägg till vågor för multi
var waveCoordinator = new WaveCoordinator(waves, profile);
Lägg till manifester för deklarativ config:
name: MyWave
triggers: [...]
emits: [...]
Här är den kompletta versionen lucidRAG Pipeline at a glance (se de detaljerade kodexexamterna i sectionen """, "Use Case, : "", och "Ljudrig RAG Document Processing," " "" och ):.
document.uploaded signalfile.extension signaltext.extracted signaldocument.chunked signalembeddings.generated signalentities.extracted signalquality.score signalescalation.complete signalstorage.complete signalInterfejsen Abonnerar SignalSink för reala framsteguppdateringar (push-mönster
// Subscribe to sink for push notifications
signalSink.Subscribe(signal => {
if (signal.Key.StartsWith("document."))
{
await _hub.Clients.User(userId)
.SendAsync("DocumentProgress", new
{
stage = signal.Key,
progress = CalculateProgress(signal),
operationId = signal.OperationId
});
}
});
Eller använda pull pattern , om du föredrar att rösta
// Query SignalSink for document progress (pull pattern)
var documentSignals = signalSink.GetSignals()
.Where(s => s.Key.StartsWith("document.") &&
s.Timestamp > lastCheck);
foreach (var signal in documentSignals)
{
UpdateProgressUI(signal);
}
Nyckelpunkt: Du Abonnerar SINK: n ( som tittar på alla atomer ), inte på enskilda operationar M SK2 Atomernas egna signaler ; som låter dig trycka MSC4 Abonnera МSK5 och dra
Det här är hur lucidRAG bearbetar dokument, data, , och bilder genom en enificerad signal, driven pipeline, DocSummarizer, DataSummarizer, och ImageSummarizer under en orkesterlag.
Här är den kompletta versionen Stylobot Pipeline at a glance (se detaljerad kod i "Escalation: From Fast to Thorough
bot.detected -signal med självförtroendebot.detected signalbot.detected signalDen viktigaste vinsten, :, escalation baserat på självförtroende.
// ❌ Traditional: Every request gets expensive analysis
var reputation = await CheckIpAsync(ip);
var behavior = await AnalyzeBehaviorAsync(session); // Even if IP is known bad
var llmScore = await LlmAnalysisAsync(conversation); // Always expensive
// ✅ StyloFlow: Waves run based on confidence signals
// BehaviorAnalysis only runs if confidence is ambiguous (0.4-0.7)
// LLM analysis only runs if still unsure after behavior check
Tilldelning av kostnaderna: IP-kontrollkostnader $0 och kör 100% av tiden. Behaviorell analys kör ♫ 30% ♫ ♫ $0.0001 i jämförelse med naivt "LLM allt " på $0.002 (20x sparar
| Funktionen | StyloFlow ♫ ♫ | ♫ Temporal ♫ | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Koordinering | Signal -driven ♫ ♫ | ♫ RPC ♫ | ||||||||||||
| Deklarativt | ✅ YAML-manifester | |||||||||||||
| Bedingande | ✅ Signaltrigger | ✅ | villkoren | МSK5 | böjning | ✅ | valval | |||||||
| Eskalation | ✅ byggt- i SSK3 | ❌ | Handbook | SSK6 | handbook | |||||||||
| Observerbarhet | ✅ Signalspårning | МSK3 | Historien av arbetsflödet | |||||||||||
| Budjetkontroll | ♫ ✅ ♫ Token ♫ | |||||||||||||
| Lokalt utförande | ✅ Iprocessen | - | 3 | 4 | Benötigt kluster | МSK6 | Befordrar kluster | |||||||
| Konkurenssbanor | ✅ Fast/NormalM SK3LLM ♫ ♫ | ♫ |
Där den här modellen passar naturligt
Där det inte fungerar
De är naturliga förlängningar av modellen, inte åtaganden att implementera den specifikt.
När semantiken stabiliseras genom utveckling av lucidRAG och Stylobot, så blir dessa mönster livskraftiga.
1. Utlärning från signaler
Träg vilka eskalationsvägar som fungerar bäst:
// Did the LLM escalation improve accuracy?
// Learn to skip it if behavioral analysis is sufficient
2. kostnadsoptimering
Automatisk körning baserat på tidigare resultat:
// If a "slow" wave completes quickly, promote to "normal"
3. Signaluppspelning
Debug genom att spela upp signalsekvenser igen:
var replay = SignalReplay.FromFile("trace.jsonl");
await coordinator.ReplayAsync(replay);
4. Multi-
Distribuera banor över maskiner samtidigt som signalerna är centraliserade.
Kerninsikten är den här: i AI-system, ,, har varje komponent självförtroende.
Traditionella arbetsflöden förutsätter framgång
Signaler ger detta naturligt:
// Multiple detectors vote
var signals = context.GetSignals("bot.detected");
// Aggregate by confidence
var verdict = signals
.OrderByDescending(s => s.Confidence)
.First();
// Or majority vote
var isBot = signals
.Count(s => (bool)s.Value) > signals.Count() / 2;
// Or weighted average
var score = signals
.Sum(s => (bool)s.Value ? s.Confidence : -s.Confidence)
/ signals.Count();
Det är därför StyloFlow fungerar bra för Reduzerad RAG - varje extraktionsstadium producerar en självförtroende poäng , och syntetiseringen sker bara när självför tro är tillräckligt hög
Ausführungsmodellen:
Varför är signaler viktiga:
Arbetsimplementeringar:
Related articles:
källkod: GitHub - StyloFlow
Traditionella arbetsflödesmotorer ber dig att vad händer sedan?. Den här modellen ber komponenterna att deklarera vad de producerar och vad de behöver, så låter signalerna koordinera utdraget
Nyckelns förändring: signaler avkopplar , förtroende vägledningar, banaer skyddar.
Det här handlar inte om att välja StyloFlow över Temporal eller Airflow, utan att - löser olika problem.
Om du bygger AI, /, ML, rör där?
"...", då passar den här tolkningssemantiken kanske till vad du tror
Den tidiga bibliotek är den stabila grunden.
För frågor eller feedback GitHubs arkiv.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.