@@ -70,7 +70,7 @@ def _run() -> None:
7070
7171 section = tier_section_config (config , kind = "tool" )
7272 hits = _lexical_shadow_hits (query , dormant_ids , tools_by_id )
73- manager .apply_shadow_tool_hits (hits , config )
73+ manager .apply_shadow_tool_hits (hits , config , tools_by_id = tools_by_id )
7474 if section .mode == TierMode .SHADOW and hits :
7575 logger .debug ("tool shadow hits: %d dormant tools" , len (hits ))
7676 except Exception :
@@ -107,19 +107,98 @@ def _run() -> None:
107107 _executor .submit (_run )
108108
109109
110+ def _entity_mcp_server (entity_id : str , tools_by_id : dict [str , dict [str , Any ]]) -> str | None :
111+ tool = tools_by_id .get (entity_id )
112+ if tool is not None :
113+ from cyt .tool_examples .identity import resolve_mcp_server_and_tool
114+
115+ server , _bare = resolve_mcp_server_and_tool (tool )
116+ return server or None
117+ if ":" not in entity_id :
118+ return None
119+ wire_name = entity_id .split (":" , 1 )[1 ]
120+ from cyt .tool_examples .identity import resolve_mcp_server_and_tool_from_wire_name
121+
122+ server , _bare = resolve_mcp_server_and_tool_from_wire_name (wire_name )
123+ return server or None
124+
125+
126+ def _lexical_skill_shadow_hits (
127+ query : str ,
128+ dormant_ids : list [str ],
129+ skills_by_id : dict [str , dict [str , Any ]],
130+ ) -> list [tuple [str , float ]]:
131+ q = query .lower ()
132+ hits : list [tuple [str , float ]] = []
133+ for entity_id in dormant_ids :
134+ skill = skills_by_id .get (entity_id )
135+ if skill is None :
136+ continue
137+ name = str (skill .get ("name" ) or skill .get ("doc_id" ) or "" ).lower ()
138+ desc = str (skill .get ("description" ) or "" ).lower ()
139+ score = 0.0
140+ if name and name in q :
141+ score = 0.85
142+ elif name :
143+ parts = [part for part in name .replace ("-" , "_" ).split ("_" ) if len (part ) > 2 ]
144+ if any (part in q for part in parts ):
145+ score = 0.55
146+ if desc :
147+ words = [word for word in desc .split () if len (word ) > 4 ][:12 ]
148+ if any (word .lower () in q for word in words ):
149+ score = max (score , 0.35 )
150+ if score > 0.0 :
151+ hits .append ((entity_id , score ))
152+ return hits
153+
154+
155+ def schedule_skill_shadow_evaluation (
156+ * ,
157+ config : dict [str , Any ],
158+ query : str ,
159+ dormant_entity_ids : list [str ],
160+ skills_by_id : dict [str , dict [str , Any ]],
161+ manager : TierManager | NoOpTierManager ,
162+ ) -> None :
163+ if not tiers_active (config , kind = "skill" ) or not query .strip ():
164+ return
165+ if not dormant_entity_ids :
166+ return
167+
168+ def _run () -> None :
169+ try :
170+ from cyt .tiers .config import tier_section_config
171+
172+ section = tier_section_config (config , kind = "skill" )
173+ hits = _lexical_skill_shadow_hits (query , dormant_entity_ids , skills_by_id )
174+ manager .apply_shadow_skill_hits (hits , config )
175+ if section .mode == TierMode .SHADOW and hits :
176+ logger .debug ("skill shadow hits: %d dormant skills" , len (hits ))
177+ except Exception :
178+ logger .exception ("skill shadow evaluation failed" )
179+
180+ with _executor_lock :
181+ _executor .submit (_run )
182+
183+
110184def record_shadow_hits (
111185 states : dict [tuple [str , str ], EntityTierState ],
112186 * ,
113187 kind : str ,
114188 hits : list [tuple [str , float ]],
115189 cfg : TierSectionConfig ,
116190 wake_cycle_id : int ,
191+ tools_by_id : dict [str , dict [str , Any ]] | None = None ,
117192) -> list [Any ]:
118193 from cyt .tiers .models import TierTransition
119194
120195 transitions : list [TierTransition ] = []
196+ woken : set [str ] = set ()
121197 hit_set = {entity_id for entity_id , score in hits if score > 0 }
122- for entity_id in hit_set :
198+
199+ def _wake_entity (entity_id : str , * , query_relevance : float , sibling_activity : float ) -> None :
200+ if entity_id in woken :
201+ return
123202 key = (kind , entity_id )
124203 state = states .get (key )
125204 if state is None :
@@ -130,21 +209,64 @@ def record_shadow_hits(
130209 effective_tier = Tier .DORMANT ,
131210 )
132211 states [key ] = state
133- state .stats .shadow_evaluations += 1.0
134- state .stats .shadow_hits += 1.0
135- top_score = max ((score for eid , score in hits if eid == entity_id ), default = 0.0 )
212+ if entity_id in hit_set :
213+ state .stats .shadow_hits += 1.0
136214 wake = evaluate_fast_wake (
137215 state ,
138216 cfg = cfg ,
139217 wake_cycle_id = wake_cycle_id ,
140- query_relevance = top_score ,
218+ query_relevance = query_relevance ,
219+ sibling_activity = sibling_activity ,
141220 )
142221 if wake is not None :
143222 transitions .append (wake )
144- for state_key in states :
145- if state_key [0 ] != kind :
146- continue
147- eid = state_key [1 ]
148- if eid not in hit_set and states [state_key ].effective_tier == Tier .DORMANT :
149- states [state_key ].stats .shadow_evaluations += 1.0
223+ woken .add (entity_id )
224+
225+ for entity_id in hit_set :
226+ key = (kind , entity_id )
227+ state = states .get (key )
228+ if state is None :
229+ state = EntityTierState (
230+ entity_id = entity_id ,
231+ kind = kind ,
232+ stable_tier = Tier .DORMANT ,
233+ effective_tier = Tier .DORMANT ,
234+ )
235+ states [key ] = state
236+ state .stats .shadow_evaluations += 1.0
237+ top_score = max ((score for eid , score in hits if eid == entity_id ), default = 0.0 )
238+ _wake_entity (entity_id , query_relevance = top_score , sibling_activity = 0.0 )
239+
240+ server_max_score : dict [str , float ] = {}
241+ if tools_by_id is not None :
242+ for entity_id in hit_set :
243+ server = _entity_mcp_server (entity_id , tools_by_id )
244+ if not server :
245+ continue
246+ top_score = max ((score for eid , score in hits if eid == entity_id ), default = 0.0 )
247+ server_max_score [server ] = max (server_max_score .get (server , 0.0 ), top_score )
248+
249+ if tools_by_id is not None and server_max_score :
250+ for state_key , state in states .items ():
251+ if state_key [0 ] != kind or state .effective_tier != Tier .DORMANT :
252+ continue
253+ entity_id = state_key [1 ]
254+ server = _entity_mcp_server (entity_id , tools_by_id )
255+ if not server or server not in server_max_score :
256+ continue
257+ if entity_id not in hit_set :
258+ state .stats .shadow_evaluations += 1.0
259+ _wake_entity (
260+ entity_id ,
261+ query_relevance = server_max_score [server ],
262+ sibling_activity = 0.0 if entity_id in hit_set else 1.0 ,
263+ )
264+ else :
265+ for state_key in states :
266+ if state_key [0 ] != kind :
267+ continue
268+ eid = state_key [1 ]
269+ if eid not in hit_set and states [state_key ].effective_tier == Tier .DORMANT :
270+ states [state_key ].stats .shadow_evaluations += 1.0
271+
150272 return transitions
0 commit comments