mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-09-05 08:06:05 +00:00
261 lines
9.7 KiB
Python
261 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import traceback
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from openspace.utils.logging import Logger
|
|
|
|
from .execution_context import ExecutionContextManager
|
|
from .execution_events import ExecutionEventEmitter
|
|
from .execution_finalizer import ExecutionFinalizer
|
|
from .execution_request import ExecutionRequest, ExecutionResult
|
|
from .execution_scheduler import ExecutionScheduler
|
|
|
|
logger = Logger.get_logger(__name__)
|
|
|
|
|
|
class ExecutionLifecycle:
|
|
"""Owns the per-request execution lifecycle for OpenSpaceRuntime.
|
|
|
|
The runtime remains the public owner of state and services. This class keeps
|
|
the request orchestration readable and delegates context, scheduler, event,
|
|
and finalization details to focused collaborators.
|
|
"""
|
|
|
|
def __init__(self, runtime: Any) -> None:
|
|
self._runtime = runtime
|
|
self.context_manager = ExecutionContextManager(runtime)
|
|
self.scheduler = ExecutionScheduler(runtime)
|
|
self.events = ExecutionEventEmitter(runtime)
|
|
self.finalizer = ExecutionFinalizer(runtime)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(self._runtime, name)
|
|
|
|
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
task = request.prompt
|
|
|
|
if not self.state.initialized:
|
|
raise RuntimeError(
|
|
"OpenSpace not initialized. "
|
|
"Call await initialize() before execute() or use async with."
|
|
)
|
|
|
|
await self.context_manager.wait_until_idle()
|
|
|
|
logger.info("=" * 60)
|
|
logger.info(f"Task: {task[:100]}...")
|
|
logger.info("=" * 60)
|
|
self.context_manager.record_interaction()
|
|
|
|
self.state.running = True
|
|
self.state.task_done.clear()
|
|
|
|
loop = asyncio.get_event_loop()
|
|
start_time = loop.time()
|
|
task_id = request.task_id or f"task_{uuid.uuid4().hex[:12]}"
|
|
logger.info(f"Task ID: {task_id}")
|
|
|
|
result: dict[str, Any] = {}
|
|
evolved_skills: list[dict[str, Any]] = []
|
|
capture_skill_dir = request.capture_skill_dir
|
|
execution_context: dict[str, Any] = {}
|
|
execution_time = 0.0
|
|
cancelled_exc: asyncio.CancelledError | None = None
|
|
memory_drain_timeout = self.context_manager.memory_drain_timeout()
|
|
|
|
try:
|
|
execution_context = self.context_manager.build_initial_context(
|
|
request,
|
|
task_id,
|
|
)
|
|
low_latency_profiler = execution_context.get("low_latency_profiler")
|
|
|
|
had_input_session_id = bool(execution_context.get("session_id"))
|
|
session_id = await self.session_runtime.prepare(execution_context)
|
|
if not self.current_session_id:
|
|
self.current_session_id = session_id
|
|
execution_context["session_id"] = session_id
|
|
if "session_start_source" not in execution_context:
|
|
if request.session_id or request.resume:
|
|
execution_context["session_start_source"] = "resume"
|
|
elif not had_input_session_id:
|
|
execution_context["session_start_source"] = "startup"
|
|
self.context_manager.attach_runtime_context(execution_context)
|
|
|
|
if self.state.multi_agent is not None:
|
|
self.state.multi_agent.inject_context(execution_context)
|
|
|
|
if request.max_iterations is not None:
|
|
execution_context["max_iterations"] = request.max_iterations
|
|
if self.state.reasoning_effort:
|
|
execution_context["reasoning_effort"] = self.state.reasoning_effort
|
|
|
|
self.context_manager.apply_config_defaults(execution_context)
|
|
self.context_manager.apply_permission_mode(execution_context)
|
|
|
|
await self.context_manager.start_recording(task_id, task)
|
|
workspace_dir = await self.context_manager.resolve_workspace(
|
|
request=request,
|
|
task_id=task_id,
|
|
execution_context=execution_context,
|
|
)
|
|
self.scheduler.install_ensure(
|
|
execution_context=execution_context,
|
|
low_latency_profiler=low_latency_profiler,
|
|
workspace_dir=workspace_dir,
|
|
)
|
|
|
|
await self.scheduler.maybe_start(
|
|
task=task,
|
|
execution_context=execution_context,
|
|
low_latency_profiler=low_latency_profiler,
|
|
workspace_dir=workspace_dir,
|
|
)
|
|
|
|
self.remember_memory_cleanup_context(execution_context)
|
|
await self.context_manager.configure_workspace(workspace_dir)
|
|
|
|
max_iterations = self.context_manager.resolve_max_iterations(
|
|
request.max_iterations
|
|
)
|
|
await self.emit_runtime_event(
|
|
"task_started",
|
|
{
|
|
"task_id": task_id,
|
|
"parent_task_id": execution_context.get("parent_task_id"),
|
|
"session_id": session_id,
|
|
"agent_id": execution_context.get("agent_id") or "primary",
|
|
"instruction": request.prompt,
|
|
"workspace_dir": workspace_dir,
|
|
"max_iterations": max_iterations,
|
|
"permission_mode": execution_context.get("permission_mode"),
|
|
"session_start_source": execution_context.get(
|
|
"session_start_source"
|
|
),
|
|
"model": self.config.llm_model,
|
|
},
|
|
)
|
|
await self.events.emit_start(
|
|
task=task,
|
|
task_id=task_id,
|
|
session_id=session_id,
|
|
max_iterations=max_iterations,
|
|
)
|
|
|
|
result = await self.events.run_turns(
|
|
task=task,
|
|
task_id=task_id,
|
|
session_id=session_id,
|
|
max_iterations=max_iterations,
|
|
execution_context=execution_context,
|
|
)
|
|
|
|
memory_drain_timeout = self.context_manager.memory_drain_timeout()
|
|
await self.drain_memory_background_tasks(
|
|
timeout_s=memory_drain_timeout,
|
|
reason="agent_finished",
|
|
context=execution_context,
|
|
)
|
|
|
|
execution_time = loop.time() - start_time
|
|
self.context_manager.apply_final_permission_mode(
|
|
result,
|
|
execution_context,
|
|
)
|
|
await self.events.emit_complete(
|
|
task=task,
|
|
task_id=task_id,
|
|
session_id=session_id,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
)
|
|
|
|
except asyncio.CancelledError as exc:
|
|
execution_time = loop.time() - start_time
|
|
logger.warning("Task execution cancelled")
|
|
await self.emit_runtime_event(
|
|
"background_session_update",
|
|
{
|
|
"session_id": self.current_session_id,
|
|
"title": task,
|
|
"status": "cancelled",
|
|
"active_agent_id": "primary",
|
|
"metadata": {
|
|
"task_id": task_id,
|
|
"execution_time": execution_time,
|
|
},
|
|
},
|
|
)
|
|
result = {
|
|
"status": "cancelled",
|
|
"error": "Task execution cancelled",
|
|
"response": "",
|
|
"execution_time": execution_time,
|
|
"task_id": task_id,
|
|
"iterations": 0,
|
|
"tool_executions": [],
|
|
}
|
|
cancelled_exc = exc
|
|
|
|
except Exception as exc:
|
|
execution_time = loop.time() - start_time
|
|
tb = traceback.format_exc(limit=10)
|
|
logger.error(f"Task execution failed: {exc}", exc_info=True)
|
|
|
|
await self.emit(
|
|
"task_error",
|
|
{
|
|
"task_id": task_id,
|
|
"error": str(exc)[:500],
|
|
"execution_time": execution_time,
|
|
},
|
|
)
|
|
await self.emit_runtime_event(
|
|
"background_session_update",
|
|
{
|
|
"session_id": self.current_session_id,
|
|
"title": task,
|
|
"status": "error",
|
|
"active_agent_id": "primary",
|
|
"metadata": {
|
|
"task_id": task_id,
|
|
"error": str(exc)[:500],
|
|
"execution_time": execution_time,
|
|
},
|
|
},
|
|
)
|
|
|
|
result = {
|
|
"status": "error",
|
|
"error": str(exc),
|
|
"traceback": tb,
|
|
"response": f"Task execution error: {str(exc)}",
|
|
"execution_time": execution_time,
|
|
"task_id": task_id,
|
|
"iterations": 0,
|
|
"tool_executions": [],
|
|
}
|
|
|
|
finally:
|
|
try:
|
|
final_result = await self.finalizer.finalize(
|
|
task_id=task_id,
|
|
start_time=start_time,
|
|
execution_time=execution_time,
|
|
result=result,
|
|
execution_context=execution_context,
|
|
memory_drain_timeout=memory_drain_timeout,
|
|
evolved_skills=evolved_skills,
|
|
capture_skill_dir=capture_skill_dir,
|
|
cancelled_exc=cancelled_exc,
|
|
)
|
|
finally:
|
|
self.state.running = False
|
|
self.state.task_done.set()
|
|
|
|
if cancelled_exc is not None:
|
|
raise cancelled_exc
|
|
return ExecutionResult.from_mapping(final_result)
|