"""极小硬核 Kernel:不变量裁决、命台并发控制、事件日志写入、Run 循环骨架。 不可插拔(lingtai-runtime.md §1);插件与 Profile 见 ``app.runtime``。 """ from __future__ import annotations import asyncio from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from app.kernel.eventlog import KernelEventLog from app.kernel.loop import KernelRunLoop, OnRunFinal from app.kernel.seat import SeatManager from app.models.runtime import Run from app.runtime.bus import WaterfallBus from app.runtime.profile import resolve_profile from app.shenshi_client.client import ShenshiClient __all__ = ["Kernel", "KernelEventLog", "KernelRunLoop", "SeatManager"] class Kernel: """门面:持有 Kernel 全部部件,供 lifespan 装配与 API 层调用。""" def __init__( self, *, shenshi: ShenshiClient, session_factory: async_sessionmaker[AsyncSession], event_log: KernelEventLog, bus: WaterfallBus | None = None, seats: SeatManager | None = None, timeout_seconds: float = 120.0, ) -> None: self._sessions = session_factory self.bus = bus or WaterfallBus() self.seats = seats or SeatManager() self.loop = KernelRunLoop( event_log=event_log, seats=self.seats, bus=self.bus, shenshi=shenshi, session_factory=session_factory, timeout_seconds=timeout_seconds, ) self._tasks: set[asyncio.Task[None]] = set() async def start_foreground_run( self, *, character_id: str, prompt: str, owner_id: str = "", trace_id: str | None = None, system_contract: str | None = None, origin: str = "kernel", on_run_final: OnRunFinal | None = None, temperature: float | None = None, max_tokens: int | None = None, ) -> Run: """创建 Run 行(queued)并异步启动循环;前台席位在循环内排队获取。""" profile = resolve_profile() async with self._sessions() as session: run = Run( character_id=character_id, owner_id=owner_id, status="queued", kind="foreground", goal=prompt[:300] if prompt else None, origin=origin, model_hint=str(profile.params.get("slot", "chat")), ) session.add(run) await session.commit() run_id = run.id self._spawn( run_id=run_id, character_id=character_id, owner_id=owner_id, prompt=prompt, trace_id=trace_id, seat_key=None, system_contract=system_contract, on_final=on_run_final, temperature=temperature, max_tokens=max_tokens, ) async with self._sessions() as session: created = await session.get(Run, run_id) assert created is not None return created async def start_backstage_run( self, *, character_id: str | None, prompt: str, owner_id: str = "", on_task_final=None, ) -> Run: """天工阁受托任务 Run(kind=background_task):不占前台席位、并行于前台。 无身份写入路径:结果只回呈任务(on_task_final),不触发记忆候选与身份写入 (息壤归真提取只挂在 canonize 链上,后台 Run 不归真)。 """ profile = resolve_profile() async with self._sessions() as session: run = Run( character_id=character_id, owner_id=owner_id, status="queued", kind="background_task", goal=prompt[:300] if prompt else None, origin="tiangong_task", model_hint=str(profile.params.get("slot", "chat")), ) session.add(run) await session.commit() run_id = run.id self._spawn( run_id=run_id, character_id=character_id, owner_id=owner_id, prompt=prompt, trace_id=None, seat_key=None, system_contract=None, on_final=on_task_final, background=True, ) async with self._sessions() as session: created = await session.get(Run, run_id) assert created is not None return created async def start_rehearsal_run( self, *, embryo_id: str, prompt: str, system_contract: str, owner_id: str = "" ) -> Run: """照心试演(幻境 Run):无正式人物、不占人物命台、不产生人物经历。""" profile = resolve_profile() async with self._sessions() as session: run = Run( character_id=None, owner_id=owner_id, status="queued", kind="rehearsal", goal=prompt[:300] if prompt else None, origin="embryo_rehearse", model_hint=str(profile.params.get("slot", "chat")), ) session.add(run) await session.commit() run_id = run.id self._spawn( run_id=run_id, character_id=None, owner_id=owner_id, prompt=prompt, trace_id=None, seat_key=f"embryo:{embryo_id}", system_contract=system_contract, on_final=None, ) async with self._sessions() as session: created = await session.get(Run, run_id) assert created is not None return created @property def shenshi(self) -> ShenshiClient: """神识客户端(息壤提取等功能任务的受控取用点)。""" return self.loop._shenshi # noqa: SLF001 def _spawn( self, *, run_id: str, character_id: str | None, owner_id: str, prompt: str, trace_id: str | None, seat_key: str | None, system_contract: str | None, on_final: OnRunFinal | None, temperature: float | None = None, max_tokens: int | None = None, background: bool = False, ) -> None: task = asyncio.create_task( self.loop.execute( run_id=run_id, character_id=character_id, owner_id=owner_id, prompt=prompt, trace_id=trace_id, seat_key=seat_key, system_contract=system_contract, on_final=on_final, temperature=temperature, max_tokens=max_tokens, background=background, ) ) self._tasks.add(task) task.add_done_callback(self._tasks.discard)