Coverage for src / harnessutils / models / velocity.py: 100%
35 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-12 22:40 -0600
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-12 22:40 -0600
1"""Conversation velocity tracking for predictive overflow detection."""
3from dataclasses import dataclass, field
6@dataclass
7class ConversationVelocity:
8 """Track token growth velocity for predictive overflow detection.
10 Tracks recent token deltas to predict future growth and trigger
11 compaction before overflow occurs.
12 """
14 turn_deltas: list[int] = field(default_factory=list)
15 window_size: int = 10 # Track last N turns
17 @property
18 def avg_growth(self) -> float:
19 """Calculate average tokens per turn.
21 Returns:
22 Average token growth across tracked turns
23 """
24 if not self.turn_deltas:
25 return 0.0
27 return sum(self.turn_deltas) / len(self.turn_deltas)
29 @property
30 def growth_trend(self) -> float:
31 """Calculate acceleration (recent vs older growth).
33 Compares recent half vs older half to detect acceleration.
35 Returns:
36 Positive = accelerating, Negative = decelerating, 0 = steady
37 """
38 if len(self.turn_deltas) < 4:
39 return 0.0 # Need at least 4 turns for trend
41 mid = len(self.turn_deltas) // 2
42 recent_avg = sum(self.turn_deltas[mid:]) / len(self.turn_deltas[mid:])
43 older_avg = sum(self.turn_deltas[:mid]) / len(self.turn_deltas[:mid])
45 return recent_avg - older_avg
47 def predict_tokens_ahead(self, turns: int) -> int:
48 """Predict token count N turns ahead.
50 Uses linear projection with acceleration boost.
52 Args:
53 turns: Number of turns to project ahead
55 Returns:
56 Predicted token count increase
57 """
58 if not self.turn_deltas:
59 return 0
61 base_prediction = self.avg_growth * turns
63 # Add acceleration boost if accelerating
64 if self.growth_trend > 0:
65 acceleration_boost = self.growth_trend * turns * 0.5
66 base_prediction += acceleration_boost
68 return int(base_prediction)
70 def add_delta(self, tokens: int) -> None:
71 """Add a new token delta.
73 Args:
74 tokens: Token count for this turn
75 """
76 self.turn_deltas.append(tokens)
78 # Keep only last window_size deltas
79 if len(self.turn_deltas) > self.window_size:
80 self.turn_deltas.pop(0)
82 def to_dict(self) -> dict:
83 """Convert to dictionary for storage.
85 Returns:
86 Dictionary representation
87 """
88 return {
89 "turn_deltas": self.turn_deltas,
90 "window_size": self.window_size,
91 }
93 @classmethod
94 def from_dict(cls, data: dict) -> "ConversationVelocity":
95 """Create from dictionary.
97 Args:
98 data: Dictionary representation
100 Returns:
101 ConversationVelocity instance
102 """
103 return cls(
104 turn_deltas=data.get("turn_deltas", []),
105 window_size=data.get("window_size", 10),
106 )