rayhpeng 9d0a42c1fb refactor(runtime): restructure runs module with new execution architecture
Major refactoring of deerflow/runtime/:
- runs/callbacks/ - new callback system (builder, events, title, tokens)
- runs/internal/ - execution internals (executor, supervisor, stream_logic, registry)
- runs/internal/execution/ - execution artifacts and events handling
- runs/facade.py - high-level run facade
- runs/observer.py - run observation protocol
- runs/types.py - type definitions
- runs/store/ - simplified store interfaces (create, delete, query, event)

Refactor stream_bridge/:
- Replace old providers with contract.py and exceptions.py
- Remove async_provider.py, base.py, memory.py

Add documentation:
- README.md and README_zh.md for runtime module

Remove deprecated:
- manager.py moved to internal/
- worker.py, schemas.py
- user_context.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 11:28:01 +08:00

241 lines
7.9 KiB
Python

"""Public runs facade."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, AsyncIterator, Callable
from deerflow.runtime.stream_bridge import StreamEvent
from .internal.execution.executor import _RunExecution
from .internal.execution.supervisor import RunSupervisor
from .internal.planner import ExecutionPlanner
from .internal.registry import RunRegistry
from .internal.streams import RunStreamService
from .internal.wait import RunWaitService, WaitErrorResult
from .observer import ObserverLike
from .store import RunCreateStore, RunDeleteStore, RunEventStore, RunQueryStore
from .types import CancelAction, RunRecord, RunSpec
class MultitaskRejectError(Exception):
"""Raised when multitask_strategy is reject and thread has inflight runs."""
pass
@dataclass(frozen=True)
class RunsRuntime:
"""Runtime dependencies needed to execute a run."""
bridge: Any
checkpointer: Any
store: Any | None
event_store: RunEventStore | None
agent_factory_resolver: Callable[[str | None], Any]
class _RegistryStatusAdapter:
"""Minimal adapter so execution can update registry-backed run status."""
def __init__(self, registry: RunRegistry) -> None:
self._registry = registry
async def set_status(self, run_id: str, status: Any, *, error: str | None = None) -> None:
await self._registry.set_status(run_id, status, error=error)
class RunsFacade:
"""
Phase 1 runs domain facade.
Provides unified interface for:
- create_background
- create_and_stream
- create_and_wait
- join_stream
- join_wait
Orchestrates registry, planner, supervisor, stream, and wait services.
Execution now flows through ExecutionPlanner + RunSupervisor rather than
the legacy RunManager create/start path.
"""
def __init__(
self,
registry: RunRegistry,
planner: ExecutionPlanner,
supervisor: RunSupervisor,
stream_service: RunStreamService,
wait_service: RunWaitService,
runtime: RunsRuntime,
observer: ObserverLike = None,
query_store: RunQueryStore | None = None,
create_store: RunCreateStore | None = None,
delete_store: RunDeleteStore | None = None,
) -> None:
self._registry = registry
self._planner = planner
self._supervisor = supervisor
self._stream = stream_service
self._wait = wait_service
self._runtime = runtime
self._observer = observer
self._query_store = query_store
self._create_store = create_store
self._delete_store = delete_store
async def create_background(self, spec: RunSpec) -> RunRecord:
"""
Create a run in background mode.
Returns immediately with the run record.
The run executes asynchronously.
"""
return await self._create_run(spec)
async def create_and_stream(
self,
spec: RunSpec,
) -> tuple[RunRecord, AsyncIterator[StreamEvent]]:
"""
Create a run and return stream.
Returns (record, stream_iterator).
"""
record = await self._create_run(spec)
stream = self._stream.subscribe(record.run_id)
return record, stream
async def create_and_wait(
self,
spec: RunSpec,
) -> tuple[RunRecord, dict[str, Any] | WaitErrorResult | None]:
"""
Create a run and wait for completion.
Returns (record, final_values_or_error).
"""
record = await self._create_run(spec)
result = await self._wait.wait_for_values_or_error(record.run_id)
return record, result
async def join_stream(
self,
run_id: str,
*,
last_event_id: str | None = None,
) -> AsyncIterator[StreamEvent]:
"""
Join an existing run stream.
Supports resumption via last_event_id.
"""
return self._stream.subscribe(run_id, last_event_id=last_event_id)
async def join_wait(
self,
run_id: str,
*,
last_event_id: str | None = None,
) -> dict[str, Any] | WaitErrorResult | None:
"""
Join an existing run and wait for completion.
"""
return await self._wait.wait_for_values_or_error(
run_id,
last_event_id=last_event_id,
)
async def cancel(
self,
run_id: str,
*,
action: CancelAction = "interrupt",
) -> bool:
"""Request cancellation for an active run."""
return await self._supervisor.cancel(run_id, action=action)
async def get_run(self, run_id: str) -> RunRecord | None:
"""Get run record by ID."""
if self._query_store is not None:
return await self._query_store.get_run(run_id)
return self._registry.get(run_id)
async def list_runs(self, thread_id: str) -> list[RunRecord]:
"""List runs for a thread."""
if self._query_store is not None:
return await self._query_store.list_runs(thread_id)
return await self._registry.list_by_thread(thread_id)
async def delete_run(self, run_id: str) -> bool:
"""Delete a run from durable storage and local runtime state."""
record = await self.get_run(run_id)
if record is None:
return False
await self._supervisor.cancel(run_id, action="interrupt")
await self._registry.delete(run_id)
if self._delete_store is not None:
return await self._delete_store.delete_run(run_id)
return True
async def _create_run(self, spec: RunSpec) -> RunRecord:
"""Create a run record and hand it to the execution backend."""
await self._apply_multitask_strategy(spec)
record = await self._registry.create(spec)
if self._create_store is not None:
await self._create_store.create_run(record)
await self._start_execution(record, spec)
return record
async def _apply_multitask_strategy(self, spec: RunSpec) -> None:
"""Apply multitask strategy before creating run."""
has_inflight = await self._registry.has_inflight(spec.scope.thread_id)
if not has_inflight:
return
if spec.multitask_strategy == "reject":
raise MultitaskRejectError(
f"Thread {spec.scope.thread_id} has inflight runs"
)
elif spec.multitask_strategy == "interrupt":
interrupted = await self._registry.interrupt_inflight(spec.scope.thread_id)
for run_id in interrupted:
await self._supervisor.cancel(run_id, action="interrupt")
async def _start_execution(self, record: RunRecord, spec: RunSpec) -> None:
"""Start run execution via planner + supervisor."""
# Update status to starting
await self._registry.set_status(record.run_id, "starting")
plan = self._planner.build(record, spec)
status_adapter = _RegistryStatusAdapter(self._registry)
agent_factory = self._runtime.agent_factory_resolver(spec.assistant_id)
async def _runner(handle) -> Any:
return await _RunExecution(
bridge=self._runtime.bridge,
run_manager=status_adapter, # type: ignore[arg-type]
record=record,
checkpointer=self._runtime.checkpointer,
store=self._runtime.store,
event_store=self._runtime.event_store,
agent_factory=agent_factory,
graph_input=plan.graph_input,
config=plan.runnable_config,
observer=self._observer,
stream_modes=plan.stream_modes,
stream_subgraphs=plan.stream_subgraphs,
interrupt_before=plan.interrupt_before,
interrupt_after=plan.interrupt_after,
handle=handle,
).run()
await self._supervisor.launch(record.run_id, runner=_runner)