diff --git a/backend/app/services/execution_service.py b/backend/app/services/execution_service.py index 8b70e75..2fbb29d 100644 --- a/backend/app/services/execution_service.py +++ b/backend/app/services/execution_service.py @@ -73,6 +73,13 @@ class ExecutionService: messages.append({"role": "user", "content": prompt}) try: + # Branch by task type + if task.task_type == "agent_loop": + return ExecutionService._execute_agent_loop( + db, tenant_id, task, worker, messages + ) + + # Single-call mode output, cost_log = LLMGateway.call( worker=worker, messages=messages, @@ -141,3 +148,75 @@ class ExecutionService: finally: worker.current_task_count = max(0, worker.current_task_count - 1) db.commit() + + @staticmethod + def _execute_agent_loop(db, tenant_id, task, worker, messages): + """Agent loop: multi-step reasoning with up to 5 iterations.""" + max_iterations = 5 + all_messages = list(messages) + final_output = "" + total_cost_cents = 0 + total_tokens = 0 + total_duration = 0 + + for i in range(max_iterations): + output, cost_log = LLMGateway.call( + worker=worker, + messages=all_messages, + db=db, + tenant_id=tenant_id, + project_id=task.project_id, + task_id=task.id, + ) + total_cost_cents += cost_log.cost_cents + total_tokens += cost_log.total_tokens + total_duration += cost_log.duration_ms + + # Check if agent wants to continue + if output.strip().startswith("NEEDS_TOOL:"): + tool_request = output.replace("NEEDS_TOOL:", "").strip() + tool_result = f"[Tool simulation] Processed: {tool_request}" + all_messages.append({"role": "assistant", "content": output}) + all_messages.append({"role": "user", "content": f"Tool result: {tool_result}\nPlease continue."}) + final_output = output + continue + else: + final_output = output + break + + # Save output + task.output_data = json.dumps( + {"content": final_output, "iterations": i + 1, "total_cost": total_cost_cents}, + ensure_ascii=False, + ) + + artifact = Artifact( + tenant_id=tenant_id, task_id=task.id, project_id=task.project_id, + title=task.title, content=final_output, artifact_type="text", + version=1, created_by_worker_id=worker.id, + ) + db.add(artifact) + db.flush() + + if task.requires_review: + review = Review( + tenant_id=tenant_id, task_id=task.id, project_id=task.project_id, + review_type="approval", status="pending", + review_content=final_output, submitted_at=datetime.utcnow(), + ) + db.add(review) + task.status = "review" + task.review_status = "pending" + result_status = "needs_review" + else: + task.status = "done" + task.completed_at = datetime.utcnow() + result_status = "success" + + db.commit() + + return TaskExecutionResult( + task_id=task.id, status=result_status, output=final_output, + token_usage=total_tokens, cost_cents=total_cost_cents, + duration_ms=total_duration, artifact_id=artifact.id, + )