diff --git a/ai-hub/app/core/orchestration/agent_loop.py b/ai-hub/app/core/orchestration/agent_loop.py index 4db1952..f7f56e6 100644 --- a/ai-hub/app/core/orchestration/agent_loop.py +++ b/ai-hub/app/core/orchestration/agent_loop.py @@ -115,8 +115,9 @@ # Stream real-time thoughts for UI observability instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() content = event.get("content", "") - if not instance.last_reasoning: instance.last_reasoning = "" - instance.last_reasoning += content + current_reasoning = instance.last_reasoning or "" + # Apply live compression to ensure 'inplace' feeling for turn headers & boilerplate + instance.last_reasoning = self._compress_reasoning(current_reasoning + content) # Forward to Swarm Registry so the Node List/Swarm Control UI sees it registry = getattr(rag_service, "node_registry_service", None) @@ -181,9 +182,15 @@ print(f"[AgentExecutor] RAG execution failed for {agent_id}: {e}") print(traceback.format_exc()) instance = db.query(AgentInstance).filter(AgentInstance.id == agent_id).first() - instance.status = "error_suspended" - instance.last_error = str(e) - db.commit() + if instance: + instance.status = "error_suspended" + instance.last_error = str(e) + db.commit() + return { + "status": "error", + "response": f"Execution failed: {str(e)}", + "reasoning": instance.last_reasoning if instance else "" + } except Exception as e: print(f"[AgentExecutor] Unhandled loop error: {e}") @@ -192,21 +199,36 @@ instance.status = "error_suspended" instance.last_error = f"Unhandled loop error: {str(e)}" db.commit() + return { + "status": "error", + "response": "Internal server error during execution.", + "reasoning": instance.last_reasoning if instance else "" + } finally: heartbeat_task.cancel() db.close() @staticmethod def _compress_reasoning(text: str) -> str: - """Deduplicates consecutive identical turn markers and boilerplate strategies.""" + """Deduplicates turn markers and aggressively collapses boilerplate.""" import re - # 1. Deduplicate consecutive turn headers that have no content between them - # (e.g. Turn 1 followed immediately by Turn 2) - turn_pattern = r"(\n\n---\n🛰️ \*\*\[Turn \d+\] thinking\.\.\.\*\*\n\n)(?=\n\n---\n🛰️ \*\*\[Turn \d+\] thinking\.\.\.\*\*\n\n)" + if not text: return "" + + # Remove consecutive 'Strategy' boilerplate and turn headers + # Use a non-greedy approach to collapse blocks of redundant turn signaling + boilerplate = r"Strategy:\s*Executing\s*orchestrated\s*tasks\s*in\s*progress\.*" + + # 1. Deduplicate consecutive turn headers (e.g. Turn 1 followed by Turn 2) + turn_pattern = r"(?s)(\n\n---\n🛰️ \*\*\[Turn \d+\] thinking\.\.\.\*\*\n\n)(?=\n\n---\n🛰️ \*\*\[Turn \d+\] thinking\.\.\.\*\*\n\n)" text = re.sub(turn_pattern, "", text) - # 2. Deduplicate consecutive identical 'Strategy:' boilerplate - strategy_pattern = r"(Strategy: Executing orchestrated tasks in progress\.\.\.\n?)(?=\s*Strategy: Executing orchestrated tasks in progress\.\.\.)" - text = re.sub(strategy_pattern, "", text) + # 2. Collapse sequences of Turn + Boilerplate Strategy (common during autonomous waits) + # Keeps only the last one in a sequence of turns that did nothing. + text = re.sub(rf"(?s)(\n\n---\n🛰️ \*\*\[Turn \d+\] thinking\.\.\.\*\*\n\n{boilerplate}\n?)+", + r"\1", text) + + # 3. Final cleanup of repeating Strategy chunks that might have survived + text = re.sub(rf"({boilerplate}\s*)+", r"Strategy: Executing orchestrated tasks in progress...\n", text) return text.strip() +