Coverage for src/lilbee/providers/fleet/contract.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-09-28 17:20 +0000

1"""Decides whether a running engine can serve a lilbee's configuration. 

2 

3The contract is the per-role models plus the engine build pin. Planner-derived 

4values (ctx, slots) are accepted from the running engine, never recomputed: 

5they vary legitimately with GPU occupancy at plan time. The one exception is 

6``chat_ctx_covers``: a live chat window smaller than what this process needs 

7cannot be adopted, since no prompt fit can grow it. ``vision_slots_cover`` 

8applies the same rule to the vision role's slot ceiling. 

9""" 

10 

11from __future__ import annotations 

12 

13from typing import TYPE_CHECKING 

14 

15from lilbee.providers.fleet.launch import InstanceLaunch 

16from lilbee.providers.roles import WorkerRole 

17 

18if TYPE_CHECKING: 

19 from collections.abc import Iterable 

20 

21 from lilbee.providers.fleet.swap_manager import SwapState 

22 

23 

24def decoded_launches(state: SwapState) -> list[InstanceLaunch] | None: 

25 """The engine's recorded launches, or ``None`` when the contract is undecodable. 

26 

27 The single decode site. Callers previously re-decoded launches bare, relying on 

28 a preceding contract_matches call to have proven decodability, so reordering or 

29 dropping that guard turned a non-match into an unhandled exception in the bind 

30 ladder. Returning ``None`` makes "undecodable" a value every caller must handle. 

31 """ 

32 try: 

33 return [InstanceLaunch.from_state(item) for item in state.launches] 

34 except (KeyError, TypeError, ValueError): 

35 return None 

36 

37 

38def served_pairs(state: SwapState) -> set[tuple[WorkerRole, str]] | None: 

39 """The (role, model) pairs the engine behind *state* serves, or ``None``.""" 

40 launches = decoded_launches(state) 

41 return None if launches is None else {(launch.role, launch.model) for launch in launches} 

42 

43 

44def chat_ctx_covers(launches: Iterable[InstanceLaunch], demanded_ctx: int) -> bool: 

45 """Whether the engine's per-slot chat window serves *demanded_ctx* tokens. 

46 

47 ``launches`` are the running engine's recorded launches. A zero demand, no 

48 chat launch, or a record without a positive chat ctx never refuses: derived 

49 values are adopted from the running engine. A window below the demand still 

50 covers when the record's ``built_ctx_target`` reaches the demand: the same 

51 planner aimed at least as high and achieved this window, so replacing the 

52 engine would rebuild the same window in a loop. A refusal sends the ladder 

53 to its replace-or-overflow decision. 

54 """ 

55 chat = [launch for launch in launches if launch.role is WorkerRole.CHAT and launch.ctx > 0] 

56 live = min((launch.ctx for launch in chat), default=0) 

57 if demanded_ctx <= 0 or live <= 0 or demanded_ctx <= live: 

58 return True 

59 built_target = min((launch.built_ctx_target for launch in chat), default=0) 

60 return built_target > 0 and demanded_ctx <= built_target 

61 

62 

63def vision_slots_cover(launches: Iterable[InstanceLaunch], demanded_slots: int) -> bool: 

64 """Whether the engine's vision slot ceiling serves *demanded_slots* at once. 

65 

66 A fitted count below the demand still covers when the builder's 

67 ``built_slots_target`` reached it: the same ceiling produced this fit, so a 

68 replacement would fit the same count in a loop. 

69 """ 

70 vision = [ 

71 launch for launch in launches if launch.role is WorkerRole.VISION and launch.slots > 0 

72 ] 

73 live = min((launch.slots for launch in vision), default=0) 

74 if demanded_slots <= 0 or live <= 0 or demanded_slots <= live: 

75 return True 

76 built_target = min((launch.built_slots_target for launch in vision), default=0) 

77 return built_target > 0 and demanded_slots <= built_target 

78 

79 

80def contract_matches(state: SwapState, wanted: Iterable[tuple[WorkerRole, str]], pin: str) -> bool: 

81 """Whether the engine behind *state* serves every wanted (role, model) pair. 

82 

83 The engine may serve more roles than asked; the engine build pin must 

84 equal *pin*, and an empty or undecodable served contract never matches. 

85 """ 

86 if state.engine_pin != pin: 

87 return False 

88 served = served_pairs(state) 

89 if not served: 

90 return False 

91 return all(pair in served for pair in wanted)