77 v0.3 - 2025-11-06 - Added adaptive workflow bias adjustments derived from drift
88 feedback.
99 v0.4 - 2025-11-07 - Logged workflow bias snapshots when drift triggers.
10+ v0.5 - 2025-11-07 - Added SLO tracking and mitigation planning for drift responses.
1011"""
1112
1213from __future__ import annotations
1516from collections import deque
1617from dataclasses import dataclass
1718from statistics import mean
18- from typing import Deque , Dict , Optional
19+ from typing import Deque , Dict , List , Optional
1920
2021from config .settings import AppConfig
2122from models .memory import ReviewRecord
@@ -36,14 +37,23 @@ class PerformanceSnapshot:
3637class SelfAdjustingController :
3738 """Monitors execution metrics and recommends adjustments."""
3839
39- def __init__ (self , config : AppConfig , window_size : int = 10 ) -> None :
40+ def __init__ (
41+ self ,
42+ config : AppConfig ,
43+ window_size : int = 10 ,
44+ latency_slo_seconds : float = 5.0 ,
45+ quality_slo : float = 0.85 ,
46+ ) -> None :
4047 self ._config = config
4148 self ._logger = LOGGER
4249 self ._window_size = window_size
4350 self ._history : Deque [PerformanceSnapshot ] = deque (maxlen = window_size )
4451 self ._last_advisory : Optional [str ] = None
4552 self ._workflow_biases : Dict [str , float ] = {}
4653 self ._decay = 0.9
54+ self ._latency_slo = max (0.1 , latency_slo_seconds )
55+ self ._quality_slo = quality_slo
56+ self ._last_plan : Dict [str , object ] = {}
4757
4858 def register_result (
4959 self ,
@@ -65,8 +75,16 @@ def register_result(
6575 snapshot .latency ,
6676 snapshot .verdict ,
6777 )
78+ slo_breaches = self ._evaluate_slos (result , review )
6879 advisory = self ._assess_drift ()
6980 self ._update_biases (selection , result , review , advisory is not None )
81+ self ._last_plan = self ._build_mitigation_plan (
82+ selection ,
83+ result ,
84+ review ,
85+ advisory ,
86+ slo_breaches ,
87+ )
7088 self ._last_advisory = advisory
7189 return advisory
7290
@@ -91,6 +109,22 @@ def _assess_drift(self) -> Optional[str]:
91109 self ._logger .warning (advisory )
92110 return advisory
93111 return None
112+ def _evaluate_slos (
113+ self ,
114+ result : TaskResult ,
115+ review : Optional [ReviewRecord ],
116+ ) -> List [str ]:
117+ """Return a list of breached service objectives for the current run."""
118+ breaches : List [str ] = []
119+ if result .latency_seconds > self ._latency_slo :
120+ breaches .append ("latency" )
121+ if (
122+ review
123+ and review .quality_score is not None
124+ and review .quality_score < self ._quality_slo
125+ ):
126+ breaches .append ("quality" )
127+ return breaches
94128
95129 def _update_biases (
96130 self ,
@@ -142,6 +176,58 @@ def _update_biases(
142176 target = reasoning_workflow if workflow == fast_workflow else fast_workflow
143177 if target :
144178 self ._bump_bias (target , 0.1 )
179+ def _build_mitigation_plan (
180+ self ,
181+ selection : WorkflowSelection ,
182+ result : TaskResult ,
183+ review : Optional [ReviewRecord ],
184+ advisory : Optional [str ],
185+ slo_breaches : List [str ],
186+ ) -> Dict [str , object ]:
187+ plan : Dict [str , object ] = {}
188+ if advisory :
189+ plan ["drift_advisory" ] = advisory
190+ if slo_breaches :
191+ plan ["slo_breaches" ] = slo_breaches
192+
193+ recommended : List [str ] = []
194+ if "latency" in slo_breaches :
195+ recommended .extend (
196+ [
197+ "reroute_to_reasoning_workflow" ,
198+ "increase_llm_timeout" ,
199+ ]
200+ )
201+ if "quality" in slo_breaches :
202+ recommended .append ("expand_context_retrieval" )
203+ if advisory :
204+ recommended .append ("trigger_memory_mitigation" )
205+ if result .latency_seconds > self ._latency_slo * 1.5 :
206+ recommended .append ("reduce_fast_workflow_bias" )
207+ if (
208+ review
209+ and review .verdict .lower ().startswith (("fail" , "reject" ))
210+ and selection .workflow != self ._find_workflow ("reasoning" )
211+ ):
212+ recommended .append ("escalate_to_reasoning" )
213+
214+ if recommended :
215+ deduped = list (dict .fromkeys (recommended ))
216+ plan ["recommended_actions" ] = deduped
217+
218+ if self ._history and (plan or slo_breaches or advisory or recommended ):
219+ latencies = [sample .latency for sample in self ._history ]
220+ negatives = sum (
221+ sample .verdict .lower ().startswith (("fail" , "reject" ))
222+ for sample in self ._history
223+ )
224+ plan ["window_metrics" ] = {
225+ "latency_avg" : round (mean (latencies ), 3 ),
226+ "window_size" : len (self ._history ),
227+ "negative_reviews" : negatives ,
228+ }
229+
230+ return plan
145231
146232 def _bump_bias (self , workflow : str , delta : float ) -> None :
147233 current = self ._workflow_biases .get (workflow , 0.0 )
@@ -167,3 +253,8 @@ def last_advisory(self) -> Optional[str]:
167253 def workflow_biases (self ) -> Dict [str , float ]:
168254 """Current controller preference weights per workflow."""
169255 return dict (self ._workflow_biases )
256+
257+ @property
258+ def last_plan (self ) -> Dict [str , object ]:
259+ """Return the last mitigation plan issued by the controller."""
260+ return dict (self ._last_plan )
0 commit comments