2121 ExplorationSignal ,
2222 GiveUp ,
2323 RequestTemplates ,
24+ RuleResolved ,
2425 SubmitRule ,
2526 _escalate ,
27+ make_augmentation_tools ,
2628 make_direct_fix_tools ,
2729 make_ephemeral_tools ,
2830 make_search_tools ,
@@ -70,6 +72,7 @@ def __init__(
7072 self ._pending_cleanup : list [Path ] = [] # Files to clean up after all retries
7173 self ._last_give_up_reason : str | None = None
7274 self ._last_done_message : str | None = None
75+ self ._escalate_next : bool = False
7376
7477 def set_gateway (self , gateway : LLMGateway ) -> None :
7578 """Set the LLM gateway (for lazy initialization)."""
@@ -223,7 +226,15 @@ def _run_conversation(
223226 search_tools = make_search_tools (self ._chroma , collection , self ._rules_dir )
224227 ephemeral_tools = make_ephemeral_tools (self ._rules_dir )
225228 validation_tools = make_validation_tools (self ._rules_dir , context , self ._action_registry )
226- all_tools = signal_tools + search_tools + ephemeral_tools + validation_tools + tools
229+ augmentation_tools = make_augmentation_tools (self ._rules_dir , context , self ._chroma )
230+ all_tools = (
231+ signal_tools
232+ + search_tools
233+ + ephemeral_tools
234+ + validation_tools
235+ + augmentation_tools
236+ + tools
237+ )
227238 if allow_escalation and self ._secondary_gateway is not None :
228239 all_tools .append (_escalate )
229240
@@ -251,14 +262,22 @@ def _run_conversation(
251262 for gw in self ._gateways :
252263 gw .set_gateway_config ({"rules_dir" : self ._rules_dir })
253264
254- signal = self ._converse (messages , all_tools )
265+ if allow_escalation and self ._escalate_next and self ._secondary_gateway is not None :
266+ self ._escalate_next = False
267+ logger .info ("Escalating to secondary model after action failure" )
268+ signal = self ._converse_on (
269+ self ._secondary_gateway , messages , all_tools , self ._default_budget
270+ )
271+ else :
272+ signal = self ._converse (messages , all_tools )
255273 result = self ._handle_signal (
256274 signal ,
257275 messages ,
258276 all_tools ,
259277 context ,
260278 collection ,
261279 allow_escalation = allow_escalation ,
280+ hint = hint ,
262281 )
263282
264283 # Reset gateway state after conversation ends
@@ -328,6 +347,7 @@ def _handle_signal(
328347 context : dict [str , Any ],
329348 collection : str ,
330349 allow_escalation : bool = False ,
350+ hint : str | None = None ,
331351 ) -> tuple [Rule | None , bool ]:
332352 """Handle exploration signal with pattern matching.
333353
@@ -350,6 +370,15 @@ def _handle_signal(
350370 logger .warning ("Exploration unsuccessful" , reason = reason )
351371 return None , True
352372
373+ case RuleResolved (summary = summary ):
374+ logger .info ("Explorer augmented existing rule" , summary = summary )
375+ # Re-check chroma — augmentation should have made an existing rule findable
376+ chroma_match = self ._check_chroma (context , collection )
377+ if chroma_match :
378+ return chroma_match , True
379+ logger .warning ("Rule resolved signal but no chroma match found" )
380+ return None , True
381+
353382 case Escalate (findings = findings ):
354383 if self ._secondary_gateway is None :
355384 logger .warning ("Escalation requested but no secondary gateway" )
@@ -370,6 +399,7 @@ def _handle_signal(
370399 context ,
371400 collection ,
372401 allow_escalation = False ,
402+ hint = hint ,
373403 )
374404
375405 case RequestTemplates ():
@@ -378,6 +408,16 @@ def _handle_signal(
378408 rules_dir = self ._rules_dir ,
379409 actions_dir = self ._rules_dir .parent / "actions" ,
380410 )
411+ templates += (
412+ "\n \n ## CRITICAL: Action Design"
413+ "\n \n Actions MUST do ONE atomic fix and return ok."
414+ " Do NOT run verification or rebuild commands inside the action."
415+ " The recovery loop handles verification, retries, and iteration."
416+ " Actions that verify internally will fail on unrelated errors"
417+ " and get rejected even when their fix was correct."
418+ )
419+ if hint :
420+ templates += f"\n \n ## Caller Constraints (MUST follow)\n \n { hint } "
381421 messages .append ({"role" : "user" , "content" : templates })
382422 signal = self ._converse (messages , tools )
383423 return self ._handle_signal (
@@ -387,6 +427,7 @@ def _handle_signal(
387427 context ,
388428 collection ,
389429 allow_escalation = allow_escalation ,
430+ hint = hint ,
390431 )
391432
392433 case SubmitRule (rule_file = rule_file , action_file = action_file ):
@@ -413,6 +454,7 @@ def _handle_signal(
413454 context ,
414455 collection ,
415456 allow_escalation = False ,
457+ hint = hint ,
416458 )
417459 return rule , True
418460
@@ -673,6 +715,19 @@ def run_direct(
673715 self ._last_give_up_reason = None
674716 self ._last_done_message = None
675717
718+ if self ._session_count >= self ._session_limit :
719+ logger .warning (
720+ "Session limit reached" , count = self ._session_count , limit = self ._session_limit
721+ )
722+ return False
723+
724+ self ._session_count += 1
725+
726+ logger .info (
727+ "Starting LLM action" ,
728+ session = f"{ self ._session_count } /{ self ._session_limit } " ,
729+ )
730+
676731 can_escalate = allow_escalation and self ._secondary_gateway is not None
677732 caller_tools = make_direct_fix_tools (self ._rules_dir ) + tools
678733
0 commit comments