🇬🇧 English version
Voir aussi : Guide comparatif ProcessTypes · Schéma YAML · Retour à l'index
Orchestration par machine à états finis (FSM)
Vue d'ensemble
Orkeon fournit un framework de machine à états finis générique (StateMachine<TState, TEvent>) dans la couche Domain, avec un circuit breaker intégré pour prévenir les boucles récursives incontrôlées lors de l'orchestration multi-agents.
La FSM est configurable via YAML (champ circuitBreaker) et spécialisée en premier lieu pour le cycle de vie d'exécution des tâches (TaskExecutionStateMachine). Le framework est générique et extensible à d'autres domaines (Crew, Agent, Flow).
Architecture
Couche Domain — Framework générique
Les composants du framework se trouvent dans Orkeon.Domain.Common.StateMachine :
| Classe | Role |
|---|---|
StateMachine<TState, TEvent> |
Moteur FSM thread-safe avec circuit breaker intégré |
StateMachineBuilder<TState, TEvent> |
API fluent pour déclarer le graphe d'états |
CircuitBreakerPolicy |
Configuration des seuils de protection |
TransitionResult<TState, TEvent> |
Résultat d'une transition (états, ordinal, timestamp) |
IStateMachine<TState, TEvent> |
Interface lecture seule pour observation |
IMutableStateMachine<TState, TEvent> |
Interface avec Fire(), TryFire(), événements |
Couche Domain — Spécialisation Task
Les composants spécifiques au cycle de vie des tâches se trouvent dans Orkeon.Domain.Task :
| Classe | Rôle |
|---|---|
TaskExecutionState |
Enum des états d'exécution (Assigned, Planning, Executing, etc.) |
TaskExecutionEvent |
Enum des événements (StartPlanning, RequestToolCall, etc.) |
TaskExecutionStateMachine |
Factory pré-configurée avec guards et graphe complet |
TaskExecutionGuardContext |
Contexte typé pour les guards (retries, tool calls, validation) |
Couche Infrastructure — Integration YAML
| Classe | Role |
|---|---|
CircuitBreakerYamlConfig |
Modele YAML pour la section circuitBreaker |
CircuitBreakerPolicyFactory |
Convertit la config YAML en CircuitBreakerPolicy + FSM |
Couche Domain — Configuration
| Classe | Rôle |
|---|---|
CircuitBreakerConfig |
DTO immutable pour la configuration circuit breaker |
Graphe d'états de la TaskExecutionStateMachine
StartPlanning BeginExecution
[Assigned] ──────────────────► [Planning] ─────────────► [Executing]
│ │ ▲
│ BeginExecution ToolCallCompleted│ │ RequestToolCall
└──────────────────────────────► [Executing] ◄─────────┘ │ (guard: budget)
│ │ ▼
SubmitFor │ │ [ToolCalling]
Validation │ │ Fail │
▼ ▼ │ ToolCallFailed
[Validating] [Failed] ◄────┘
│ │ │
ValidationPassed│ │ │ Retry (guard: maxRetries)
▼ │ └──────────► [Executing]
[Completed] │ ValidationFailed
│ (guard: maxValidationRetries)
└──────────► [Executing]
RequestHumanInput HumanInputReceived
[Executing] ──────────► [WaitingForHumanInput] ──────────► [Executing]
Cancel (depuis tout état non-terminal) ────────────────► [Cancelled]
Circuit breaker trip ──────────────────────────────────► [Degraded]
États terminaux : Completed, Cancelled, Degraded.
Circuit breaker
Le circuit breaker est intégré directement dans StateMachine<TState, TEvent> et vérifie quatre conditions avant chaque transition :
Mécanismes de protection
| Mécanisme | Paramètre | Description |
|---|---|---|
| Max transitions | MaxTransitions |
Nombre total de transitions autorisées |
| Timeout par état | StateTimeout |
Durée maximale dans un seul état |
| Détection de cycles | MaxStateVisits |
Nombre max de visites d'un même état |
| Durée totale | MaxTotalDuration |
Durée de vie maximale de la machine |
Quand une condition est violée, deux comportements sont possibles selon UseDegradedMode :
false(défaut) : uneCircuitBrokenExceptionest levée avec unCircuitBreakerStatusdétaillétrue: la machine transite automatiquement vers l'état dégradé configuré (ex:TaskExecutionState.Degraded)
Presets
Trois presets sont fournis via CircuitBreakerPolicy :
| Preset | MaxTransitions | StateTimeout | MaxStateVisits | MaxTotalDuration | DegradedMode |
|---|---|---|---|---|---|
Strict |
50 | 2 min | 5 | 10 min | true |
Default |
100 | 5 min | 10 | 30 min | false |
Permissive |
1000 | 30 min | 50 | 2 h | false |
Le preset Strict est le défaut pour les workloads LLM en production.
Observabilité
La machine expose deux événements :
OnTransition: émis après chaque transition réussie, avecTransitionResult(from, to, trigger, ordinal, timestamp)OnCircuitBroken: émis quand le circuit trip, avecCircuitBreakerStatusincluant un histogramme des visites par état
CircuitBreakerStatus fournit un snapshot complet : IsBroken, BrokenReason, TotalTransitions, TimeInCurrentState, CurrentStateVisitCount, TotalElapsed, StateVisitHistogram.
Guards typés
La TaskExecutionStateMachine utilise trois guards typés via TaskExecutionGuardContext pour prévenir les boucles dangereuses :
| Guard | Transition protégée | Condition |
|---|---|---|
| Budget tool calls | Executing → ToolCalling |
ToolCallCount < MaxToolCallsPerRound && IsToolRegistered |
| Limite de retries | Failed → Executing |
RetryCount < MaxRetries |
| Limite de validation | Validating → Executing |
ValidationAttempts < MaxValidationRetries |
Le guard IsToolRegistered bloque les appels à des outils non enregistrés dans l'agent, ce qui prévient les hallucinations d'outils par le LLM.
Configuration YAML
Schéma circuitBreaker
Le bloc circuitBreaker est utilisable à deux niveaux dans le config.yaml :
# Niveau crew — défauts pour toutes les tâches
circuitBreaker:
preset: string # "strict" | "permissive" | "default"
maxTransitions: int # Surcharge le preset
stateTimeoutSeconds: int # Timeout par état en secondes
maxStateVisits: int # Détection de cycles
maxTotalDurationSeconds: int # Durée totale en secondes
useDegradedMode: bool # true = Degraded, false = exception
maxRetries: int # Retries après échec (guard)
maxToolCallsPerRound: int # Tool calls max par round (guard)
maxValidationRetries: int # Boucles validation max (guard)
tasks:
<task_id>:
description: string
# Niveau task — override pour cette tâche spécifique
circuitBreaker:
maxTransitions: int # Surcharge le défaut crew
stateTimeoutSeconds: int
# ... mêmes champs que ci-dessus
Hiérarchie de résolution
1. Task-level circuitBreaker (priorité haute)
2. Crew-level circuitBreaker (défaut)
3. Preset nommé (si spécifié)
4. CircuitBreakerPolicy.Strict (fallback si rien n'est configuré)
Chaque champ individuel surcharge la valeur du preset. Par exemple, un preset "strict" avec maxTransitions: 200 garde toutes les valeurs du preset sauf les transitions.
Modèles YAML
| Modèle C# | Classe YAML | Fichier |
|---|---|---|
CircuitBreakerConfig |
CircuitBreakerYamlConfig |
Configuration/Yaml/YamlConfigModels.cs |
Le mapping est effectué par YamlCrewMapper.MapCircuitBreaker() (privé, Configuration/Yaml/). La conversion en CircuitBreakerPolicy exécutable est effectuée par CircuitBreakerPolicyFactory.Resolve().
Utilisation en code C#
Création manuelle (Fluent Builder)
using Orkeon.Domain.Common.StateMachine;
using Orkeon.Domain.Task;
// Créer une FSM avec le preset Strict
var fsm = TaskExecutionStateMachine.Create(CircuitBreakerPolicy.Strict);
// Observer les transitions
fsm.OnTransition += (_, result) =>
Console.WriteLine($"{result.FromState} -> {result.ToState} via {result.Trigger}");
fsm.OnCircuitBroken += (_, status) =>
Console.WriteLine($"CIRCUIT BROKEN: {status.BrokenReason}");
// Contexte de garde
var ctx = new TaskExecutionGuardContext
{
RetryCount = 0,
MaxRetries = 3,
ToolCallCount = 0,
MaxToolCallsPerRound = 10,
IsToolRegistered = true,
};
// Exécuter le workflow
fsm.Fire(TaskExecutionEvent.BeginExecution);
fsm.Fire(TaskExecutionEvent.RequestToolCall, ctx);
fsm.Fire(TaskExecutionEvent.ToolCallCompleted);
fsm.Fire(TaskExecutionEvent.SubmitForValidation);
fsm.Fire(TaskExecutionEvent.ValidationPassed);
Console.WriteLine(fsm.CurrentState); // Completed
Console.WriteLine(fsm.IsTerminal); // true
Création depuis la configuration YAML
using Orkeon.Infrastructure.Configuration;
// La CircuitBreakerPolicyFactory résout la hiérarchie crew + task
var fsm = CircuitBreakerPolicyFactory.CreateTaskFsm(
crewDefault: crewConfig.CircuitBreaker,
taskOverride: taskConfig.CircuitBreaker
);
var guardCtx = CircuitBreakerPolicyFactory.CreateGuardContext(
crewDefault: crewConfig.CircuitBreaker,
taskOverride: taskConfig.CircuitBreaker
);
Construction d'une FSM custom
Le framework générique permet de définir n'importe quel graphe d'états :
var fsm = new StateMachineBuilder<MyState, MyEvent>()
.WithInitialState(MyState.Idle)
.WithTerminalStates(MyState.Done, MyState.Error)
.WithDegradedState(MyState.Error)
.WithCircuitBreaker(new CircuitBreakerPolicy
{
MaxTransitions = 50,
StateTimeout = TimeSpan.FromMinutes(2),
MaxStateVisits = 5,
UseDegradedMode = true
})
.When(MyState.Idle, MyEvent.Start)
.TransitionTo(MyState.Processing)
.WithGuard<MyContext>(ctx => ctx.IsReady, "Must be ready")
.WithAction((from, to) => Log($"{from} -> {to}"))
.Done()
.When(MyState.Processing, MyEvent.Complete)
.TransitionTo(MyState.Done)
.Done()
.Build();
Exemple 103
L'exemple examples/06-engineering-devops/103-ts-codebase-with-fsm/ démontre l'intégration complète. Il reprend le scénario de l'exemple 102 (analyse de codebase TypeScript) en ajoutant :
circuitBreakerau niveau crew avec preset "strict" et mode dégradécircuitBreakerau niveau task pour le superviseur (limites relevées car orchestration longue)- Guards anti-hallucination d'outils (
maxToolCallsPerRound: 30) - Limite de retries réduite pour le superviseur (
maxRetries: 2)
Voir le fichier config.yaml de l'exemple pour la syntaxe complète.
Relation avec l'existant
StateTransitionManager
Le StateTransitionManager existant (Orkeon.Application.Services.StateManagement) reste en place. Il valide les transitions d'états persistés (AgentStatus, CrewStatus, TaskStatus). La FSM TaskExecutionStateMachine opère à un niveau de granularité différent : elle gère le cycle d'exécution runtime (Assigned → Executing → ToolCalling → Validating → Completed) tandis que le StateTransitionManager gère les états de lifecycle (Pending → InProgress → Completed).
Les deux sont complémentaires : le StateTransitionManager gouverne le statut persisté, la FSM gouverne l'exécution en cours.
SequentialCrewOrchestrator
L'orchestrateur existant n'est pas remplacé. La FSM s'intègre à l'intérieur des IProcessStrategy (Sequential, Hierarchical, Parallel) pour piloter l'exécution de chaque tâche individuelle, là où les boucles LLM sont les plus dangereuses.
Tests
32+ tests unitaires couvrent le framework et la spécialisation :
| Fichier de test | Couverture |
|---|---|
StateMachineTests.cs |
Transitions valides/invalides, états terminaux, cycle complet |
StateMachineGuardTests.cs |
Guards typés, priorité des guards, actions on-transition |
CircuitBreakerTests.cs |
Max transitions, détection de cycles, mode dégradé, reset, histogramme |
TaskExecutionStateMachineTests.cs |
Happy path, tool calls, retries, validation, cancel, circuit breaker |
dotnet test tests/core/Orkeon.Domain.Tests/ --filter "FullyQualifiedName~StateMachine"