"""The project-owned ``ShardEngine`` contract (DGR-031). A worker process (the gRPC surface in ``shard_runtime_server.py``, or any future transport) never talks to llama.cpp directly. It talks to a ``ShardEngine``. This module is the *only* place that boundary is defined, and every operation on it is built from project-owned dataclasses and plain Python values (``str``, ``int``, ``bytes``, ``Mapping``) — never a ``ggml_tensor``, a llama context/scheduler handle, or a generated-protobuf (ABI) message. A fake fixture engine (DGR-032) and a real llama.cpp-backed engine (DGR-037) are both, structurally, nothing more than subclasses of :class:`ShardEngine`; the worker code that calls them does not change when one replaces the other. This is deliberately a fourth, distinct layer from the three that already exist: - ``native_protocol`` — the generated gRPC/Protobuf wire ABI (DGR-021/024). - ``protocol.ActivationEnvelope`` — the versioned wire envelope for activation traffic between shard *hops* over the network (DGR-021). - ``shard_lifecycle`` — the versioned RPC/session lifecycle contract a generated gRPC binding consumes (DGR-022). ``ShardEngine`` sits *inside* one worker process, below all three: it is the seam between "the code that speaks Meshnet's wire protocol" and "the code that actually runs model layers." It reuses :class:`~meshnet_node.shard_lifecycle.StructuredStatus`, :class:`~meshnet_node.shard_lifecycle.StatusCode`, :class:`~meshnet_node.shard_lifecycle.CacheExpectation`, and :class:`~meshnet_node.shard_lifecycle.CacheResult` rather than inventing a parallel status vocabulary, since those are already project-owned and version-stable. """ from __future__ import annotations import abc from dataclasses import dataclass, field from typing import Any, Mapping from .shard_lifecycle import ( CacheExpectation, CacheResult, StatusCode, StructuredStatus, ) __all__ = [ "EngineError", "EngineTensor", "BoundaryBundle", "TokenOutput", "MtpHook", "ArchitectureAuxStateHook", "LoadRequest", "LoadResult", "EngineCapabilities", "PrefillRequest", "DecodeRequest", "StepResult", "HealthResult", "MetricsResult", "ShardEngine", ] class EngineError(RuntimeError): """An engine-boundary failure represented by a structured status. Mirrors :class:`~meshnet_node.shard_lifecycle.LifecycleContractError`: callers pattern-match on ``error.status.code`` rather than on exception subclasses, so a fake and a real engine can fail the exact same way for the exact same reason. """ def __init__(self, status: StructuredStatus) -> None: self.status = status super().__init__(status.message) @dataclass(frozen=True) class EngineTensor: """One named tensor crossing the engine boundary. Intentionally not a ``ggml_tensor`` or a framework tensor object: ``data`` is plain owned bytes, ``shape``/``dtype`` are plain metadata. An implementation constructs this from whatever internal representation it uses (a ``torch.Tensor``, a llama.cpp buffer, a synthetic fixture array) without leaking that representation across the boundary. """ name: str shape: tuple[int, ...] dtype: str data: bytes def __post_init__(self) -> None: if not self.name: raise ValueError("engine tensor requires a name") if not self.shape or any(dim <= 0 for dim in self.shape): raise ValueError("engine tensor shape must be a non-empty tuple of positive ints") if not self.dtype: raise ValueError("engine tensor requires a dtype") @dataclass(frozen=True) class BoundaryBundle: """A named-tensor activation crossing a shard boundary (head/middle/tail-in). ``token_id_sideband`` carries token IDs alongside the activation only where the architecture boundary requires them (V4's first three hash-routed MoE layers); it is ``None`` everywhere else. Per-shard hot KV/recurrent/CSA/HCA/SWA/indexer/compressor state never appears here — it stays local to a shard via :class:`ArchitectureAuxStateHook` and is never part of what crosses the wire. """ tensors: tuple[EngineTensor, ...] architecture: str boundary_point: str token_id_sideband: tuple[int, ...] | None = None def __post_init__(self) -> None: if not self.tensors: raise ValueError("boundary bundle requires at least one tensor") if not self.architecture: raise ValueError("boundary bundle requires an architecture name") if not self.boundary_point: raise ValueError("boundary bundle requires a boundary point name") def tensor(self, name: str) -> EngineTensor: for tensor in self.tensors: if tensor.name == name: return tensor raise KeyError(name) @dataclass(frozen=True) class TokenOutput: """A tail shard's sampled decode result. Never a raw logits tensor: the engine boundary only ever hands back the already-sampled token (mirroring :meth:`meshnet_node.architecture_boundary.TailOutput.sampled_token`, which likewise refuses anything but a sampled token id). """ token_id: int text: str | None = None def __post_init__(self) -> None: if self.token_id < 0: raise ValueError("sampled token id must be non-negative") @dataclass(frozen=True) class MtpHook: """Reserved multi-token-prediction hook — typed, but refused when enabled. RALPH-CONTEXT is explicit that "MTP is reserved and off for alpha; its ownership contract, implementation, and benchmark are required before beta" (DGR-065/DGR-066). Reserving the shape now means DGR-037's real engine and DGR-051's V4 adapter do not have to change this dataclass's field layout later; they only flip ``enabled`` once DGR-066 lands. """ enabled: bool = False draft_token_count: int = 0 aux_state: Mapping[str, Any] | None = None def __post_init__(self) -> None: if self.enabled: raise ValueError( "MTP is reserved and must remain disabled before DGR-066; " "this hook exists to fix its shape, not to enable it" ) if self.draft_token_count < 0: raise ValueError("draft_token_count must be non-negative") @dataclass(frozen=True) class ArchitectureAuxStateHook: """Reserved per-shard architecture auxiliary-state hook. Covers V4's CSA/HCA/SWA/indexer/compressor state and any other architecture-local state a future adapter needs. RALPH-CONTEXT locks this as shard-local, keyed by route session/epoch, and explicitly never carried over the WAN seam — so this hook has no wire encoding of its own and must never be embedded inside a :class:`BoundaryBundle`. """ kind: str = "" state: Mapping[str, Any] | None = None @dataclass(frozen=True) class LoadRequest: """One exact artifact/recipe/range identity for a worker to load.""" artifact_path: str shard_start: int shard_end: int total_layers: int recipe: Mapping[str, Any] = field(default_factory=dict) def __post_init__(self) -> None: if not self.artifact_path: raise ValueError("load request requires an artifact path") if self.shard_start < 0 or self.shard_end < self.shard_start: raise ValueError("shard_start must be <= shard_end and non-negative") if self.total_layers <= self.shard_end: raise ValueError("total_layers must exceed shard_end (shard_end is inclusive)") @dataclass(frozen=True) class LoadResult: status: StructuredStatus effective_start: int = 0 architecture: str = "" @dataclass(frozen=True) class EngineCapabilities: status: StructuredStatus shard_start: int = 0 shard_end: int = 0 effective_start: int = 0 total_layers: int = 0 architecture: str = "" max_concurrent_sessions: int = 0 max_context_tokens: int = 0 supports_mtp: bool = False @property def is_head(self) -> bool: return self.shard_start == 0 @property def is_tail(self) -> bool: return self.shard_end >= self.total_layers - 1 @dataclass(frozen=True) class PrefillRequest: """A prefill step. Exactly one of ``token_ids`` (head) or ``input`` (middle/tail) is set.""" session_id: str route_epoch: int position: int idempotency_step: int token_ids: tuple[int, ...] | None = None input: BoundaryBundle | None = None cache_expectation: CacheExpectation = CacheExpectation.NONE mtp: MtpHook = field(default_factory=MtpHook) architecture_aux_state: ArchitectureAuxStateHook | None = None def __post_init__(self) -> None: _require_exactly_one_input(self.token_ids, self.input) if not self.session_id: raise ValueError("prefill request requires a session id") if self.route_epoch < 0 or self.position < 0 or self.idempotency_step < 0: raise ValueError("route_epoch, position, and idempotency_step must be non-negative") @dataclass(frozen=True) class DecodeRequest: """A decode step. Exactly one of ``token_id`` (head) or ``input`` (middle/tail) is set.""" session_id: str route_epoch: int position: int idempotency_step: int token_id: int | None = None input: BoundaryBundle | None = None mtp: MtpHook = field(default_factory=MtpHook) architecture_aux_state: ArchitectureAuxStateHook | None = None def __post_init__(self) -> None: _require_exactly_one_input( None if self.token_id is None else (self.token_id,), self.input ) if not self.session_id: raise ValueError("decode request requires a session id") if self.route_epoch < 0 or self.position < 0 or self.idempotency_step < 0: raise ValueError("route_epoch, position, and idempotency_step must be non-negative") def _require_exactly_one_input( token_ids: tuple[int, ...] | None, bundle: BoundaryBundle | None ) -> None: if (token_ids is None) == (bundle is None): raise ValueError("exactly one of token ids or a boundary bundle must be set") @dataclass(frozen=True) class StepResult: """The result of a prefill or decode step. ``output`` is a :class:`BoundaryBundle` for a head/middle shard handing an activation to the next hop, or a :class:`TokenOutput` for a tail shard that sampled a token. It is ``None`` only when ``status.code`` is not ``OK``. """ status: StructuredStatus cache_result: CacheResult = CacheResult.NOT_REQUESTED output: BoundaryBundle | TokenOutput | None = None def __post_init__(self) -> None: if self.status.code is StatusCode.OK and self.output is None: raise ValueError("a successful step result must carry an output") @dataclass(frozen=True) class HealthResult: status: StructuredStatus serving: bool = False state: str = "UNKNOWN" active_sessions: int = 0 @dataclass(frozen=True) class MetricsResult: status: StructuredStatus active_sessions: int = 0 queued_frames: int = 0 inflight_bytes: int = 0 kv_entries: int = 0 generated_tokens: int = 0 cancelled_sessions: int = 0 class ShardEngine(abc.ABC): """The contract every shard execution engine (fake or real) must implement. Every method returns a project-owned result carrying a :class:`~meshnet_node.shard_lifecycle.StructuredStatus` rather than raising for expected, protocol-visible outcomes (a cache miss, a stale epoch, an unknown session); an :class:`EngineError` is reserved for genuine programming errors at the call site (malformed request objects), which the request dataclasses' own ``__post_init__`` validation already catches before an implementation ever sees them. """ @abc.abstractmethod def load(self, request: LoadRequest) -> LoadResult: """Load one exact artifact/recipe/range identity. Idempotent per engine instance.""" @abc.abstractmethod def capabilities(self) -> EngineCapabilities: """Report this engine's authoritative range and limits after ``load``.""" @abc.abstractmethod def prefill(self, request: PrefillRequest) -> StepResult: """Run one prefill step for a session.""" @abc.abstractmethod def decode(self, request: DecodeRequest) -> StepResult: """Run one decode step for a session.""" @abc.abstractmethod def cancel(self, session_id: str, *, work_id: str = "", reason: str = "") -> StructuredStatus: """Cancel a session (or one work item within it) in flight.""" @abc.abstractmethod def release(self, session_id: str) -> StructuredStatus: """Release a session's held state. Idempotent.""" @abc.abstractmethod def health(self) -> HealthResult: """Report liveness/serving state. Must never raise.""" @abc.abstractmethod def metrics(self) -> MetricsResult: """Report point-in-time operational counters. Must never raise."""