Run evolution as a token-driven agent loop. Modeled after ``GroundingAgent.process()`` — the loop continues until the LLM outputs an explicit completion/failure token, NOT based on whether tools were called. Termination signals (checked every iteration, regardless o
(
self,
prompt: str,
ctx: EvolutionContext,
)
| 1082 | return None |
| 1083 | |
| 1084 | async def _run_evolution_loop( |
| 1085 | self, |
| 1086 | prompt: str, |
| 1087 | ctx: EvolutionContext, |
| 1088 | ) -> Optional[str]: |
| 1089 | """Run evolution as a token-driven agent loop. |
| 1090 | |
| 1091 | Modeled after ``GroundingAgent.process()`` — the loop continues |
| 1092 | until the LLM outputs an explicit completion/failure token, NOT |
| 1093 | based on whether tools were called. |
| 1094 | |
| 1095 | Termination signals (checked every iteration, regardless of tool use): |
| 1096 | - ``EVOLUTION_COMPLETE`` in assistant content → success, return edit. |
| 1097 | - ``EVOLUTION_FAILED`` in assistant content → failure, return None. |
| 1098 | |
| 1099 | Tool availability: |
| 1100 | - Iterations 1 … N-1: tools enabled (LLM may gather information). |
| 1101 | - Iteration N (final): tools disabled, LLM must output a decision. |
| 1102 | |
| 1103 | Each non-final iteration without a token gets a nudge message |
| 1104 | telling the LLM which iteration it is on and how many remain. |
| 1105 | |
| 1106 | Conversations are recorded to ``conversations.jsonl`` via |
| 1107 | ``RecordingManager`` (agent_name="SkillEvolver") so the full |
| 1108 | evolution dialogue is preserved for debugging and replay. |
| 1109 | """ |
| 1110 | from openspace.recording import RecordingManager |
| 1111 | |
| 1112 | model = self._model or self._llm_client.model |
| 1113 | |
| 1114 | # Merge tools from context and instance-level |
| 1115 | evolution_tools: List["BaseTool"] = list(ctx.available_tools or []) |
| 1116 | if not evolution_tools: |
| 1117 | evolution_tools = list(self._available_tools) |
| 1118 | |
| 1119 | messages: List[Dict[str, Any]] = [ |
| 1120 | {"role": "user", "content": prompt}, |
| 1121 | ] |
| 1122 | |
| 1123 | # Record initial conversation setup |
| 1124 | await RecordingManager.record_conversation_setup( |
| 1125 | setup_messages=copy.deepcopy(messages), |
| 1126 | tools=evolution_tools if evolution_tools else None, |
| 1127 | agent_name="SkillEvolver", |
| 1128 | extra={ |
| 1129 | "evolution_type": ctx.suggestion.evolution_type.value, |
| 1130 | "trigger": ctx.trigger.value, |
| 1131 | "target_skills": ctx.suggestion.target_skill_ids, |
| 1132 | }, |
| 1133 | ) |
| 1134 | |
| 1135 | for iteration in range(_MAX_EVOLUTION_ITERATIONS): |
| 1136 | is_last = iteration == _MAX_EVOLUTION_ITERATIONS - 1 |
| 1137 | |
| 1138 | # Snapshot message count before any additions + LLM call |
| 1139 | msg_count_before = len(messages) |
| 1140 | |
| 1141 | # Final round: disable tools and force a decision |
no test coverage detected