@@ -808,6 +808,21 @@ def success_rate_since(self, since_ts: int) -> Tuple[int, int, int]:
808808 total = success + fail
809809 return (success , fail , total )
810810
811+ def running_subagents_since (self , since_ts : int ) -> int :
812+ """Count sub-agent events launched since since_ts that have not yet completed.
813+
814+ A sub-agent is 'running' if its row in agent_events still has outcome='unknown'
815+ (i.e. we have not yet observed a tool.execution_complete for it) and it was
816+ started recently enough that it is plausibly still in flight.
817+ """
818+ cur = self ._con .cursor ()
819+ cur .execute (
820+ "SELECT COUNT(*) FROM agent_events WHERE ts>=? AND outcome = 'unknown'" ,
821+ (since_ts ,),
822+ )
823+ row = cur .fetchone ()
824+ return int (row [0 ]) if row and row [0 ] is not None else 0
825+
811826 # Feature 2: Model distribution
812827 def model_distribution_since (self , since_ts : int ) -> Dict [str , int ]:
813828 """Return model->count for events since timestamp."""
@@ -895,6 +910,9 @@ class PulseMetrics:
895910 cost_estimate_today : float = 0.0
896911 # Feature 4: Fleet health score
897912 health_score : int = 100
913+ # Real-time sub-agent tracking
914+ running_subagents : int = 0
915+ subagents_last5m : int = 0
898916
899917 def __post_init__ (self ):
900918 if self .active_session_list is None :
@@ -986,6 +1004,12 @@ def poll(self) -> PulseMetrics:
9861004 notes = {"sessions_by_tty" : sessions_by_tty , "running_agents_est" : running_agents_est },
9871005 )
9881006
1007+ # Real-time sub-agent counts (in-flight = started, not yet completed, <30min old)
1008+ running_subagents_from_events = self .store .running_subagents_since (ts - 30 * 60 )
1009+ # Combine with process-based signal; take the max so we never under-count.
1010+ running_subagents = max (running_subagents_from_events , running_agents_est )
1011+ subagents_last5m = agent_events_last5m
1012+
9891013 # Feature 1: Success rate
9901014 success_count , fail_count , rate_total = self .store .success_rate_since (ts - 24 * 3600 )
9911015 success_rate_24h = round (success_count / rate_total , 3 ) if rate_total > 0 else 0.0
@@ -1035,6 +1059,8 @@ def poll(self) -> PulseMetrics:
10351059 tokens_today = tokens_today ,
10361060 cost_estimate_today = cost_estimate_today ,
10371061 health_score = health_score ,
1062+ running_subagents = running_subagents ,
1063+ subagents_last5m = subagents_last5m ,
10381064 )
10391065
10401066
@@ -1128,23 +1154,28 @@ def bar(val: int, max_val: int = 20, width: int = 16) -> Text:
11281154 t .add_column (justify = "right" )
11291155 t .add_column (justify = "left" )
11301156 t .add_row (
1131- Text ("Active Sessions :" , style = "bold white" ),
1157+ Text ("Agents (sessions) :" , style = "bold white" ),
11321158 Text (str (m .active_sessions ), style = "bold #7CFF6B" ),
11331159 bar (m .active_sessions ),
11341160 )
11351161 t .add_row (
1136- Text ("Running Agents :" , style = "bold white" ),
1137- Text (str (m .running_agents_est ), style = "bold #B388FF" ),
1138- bar (m .running_agents_est ),
1162+ Text ("Sub-Agents running:" , style = "bold white" ),
1163+ Text (str (m .running_subagents ), style = "bold #B388FF" ),
1164+ bar (m .running_subagents ),
1165+ )
1166+ t .add_row (
1167+ Text ("Sub-Agents (5m) :" , style = "bold white" ),
1168+ Text (str (m .subagents_last5m ), style = "bold #00F5D4" ),
1169+ bar (m .subagents_last5m ),
11391170 )
11401171 t .add_row (
1141- Text ("Velocity :" , style = "bold white" ),
1172+ Text ("Velocity :" , style = "bold white" ),
11421173 Text (f"{ m .velocity } /hr" , style = "bold #FFD166" ),
11431174 Text (f"peak: { m .peak_velocity } /hr" , style = "#8D99AE" ),
11441175 )
11451176 trend = "▲ trending up" if m .spawned_today > 0 else "—"
11461177 t .add_row (
1147- Text ("Today's Total :" , style = "bold white" ),
1178+ Text ("Sub-Agents today :" , style = "bold white" ),
11481179 Text (str (m .spawned_today ), style = "bold #FF4D6D" ),
11491180 Text (trend , style = "#8D99AE" ),
11501181 )
@@ -1185,16 +1216,15 @@ def render(self) -> Panel:
11851216 Text ("7d" , style = "bold #8D99AE" ),
11861217 Text ("30d" , style = "bold #8D99AE" ),
11871218 )
1188- # Sessions row
1219+ # Agents row (top-level sessions) + Sub-Agents row (task-tool launches)
11891220 t .add_row (
11901221 Text ("Sessions" , style = "bold #00D1FF" ),
11911222 Text (str (m .sessions_today ), style = "bold #FFD166" ),
11921223 Text (str (m .sessions_week ), style = "bold #00F5D4" ),
11931224 Text (str (m .sessions_month ), style = "bold #B388FF" ),
11941225 )
1195- # Agents row
11961226 t .add_row (
1197- Text ("Agents" , style = "bold #7CFF6B" ),
1227+ Text ("Sub- Agents" , style = "bold #7CFF6B" ),
11981228 Text (str (m .spawned_today ), style = "bold #FFD166" ),
11991229 Text (str (m .spawned_week ), style = "bold #00F5D4" ),
12001230 Text (str (m .spawned_month ), style = "bold #B388FF" ),
@@ -1206,11 +1236,11 @@ def render(self) -> Panel:
12061236 t2 .add_column (justify = "left" )
12071237 t2 .add_column (justify = "left" )
12081238 t2 .add_row (
1209- Text ("14d agents " , style = "bold #7CFF6B" ),
1239+ Text ("14d sub- agents" , style = "bold #7CFF6B" ),
12101240 Text (sparkline (daily_agents , width = 14 ), style = "#7CFF6B" ),
12111241 )
12121242 t2 .add_row (
1213- Text ("14d sessions" , style = "bold #00D1FF" ),
1243+ Text ("14d sessions " , style = "bold #00D1FF" ),
12141244 Text (sparkline (daily_sessions , width = 14 ), style = "#00D1FF" ),
12151245 )
12161246
@@ -1222,7 +1252,7 @@ def render(self) -> Panel:
12221252 else :
12231253 trend = Text (" ► steady" , style = "#8D99AE" )
12241254
1225- all_time = Text .assemble (("All-time: " , "#8D99AE" ), (str (m .spawned_all_time ), "bold #00D1FF" ), (" agents" , "#8D99AE" ))
1255+ all_time = Text .assemble (("All-time: " , "#8D99AE" ), (str (m .spawned_all_time ), "bold #00D1FF" ), (" sub- agents" , "#8D99AE" ))
12261256
12271257 return Panel (Group (t , t2 , trend , all_time ), border_style = "#FF4D6D" , title = "[bold #00D1FF]📊 TREND ANALYSIS[/]" )
12281258
@@ -1233,14 +1263,20 @@ class MixPanel(Static):
12331263 def render (self ) -> Panel :
12341264 m = self .metrics
12351265 if not m :
1236- return Panel ("…" , border_style = "#7CFF6B" , title = "[bold #FFD166]◆ AGENT BREAKDOWN[/]" )
1266+ return Panel ("…" , border_style = "#7CFF6B" , title = "[bold #FFD166]◆ SUB- AGENT BREAKDOWN[/]" )
12371267
12381268 by_type = dict (m .spawned_by_type_24h )
1269+ total_24h = sum (by_type .values ())
1270+ title = (
1271+ f"[bold #FFD166]◆ SUB-AGENT BREAKDOWN[/] "
1272+ f"[#8D99AE]· running[/] [bold #B388FF]{ m .running_subagents } [/] "
1273+ f"[#8D99AE]· 24h[/] [bold #FFD166]{ total_24h } [/]"
1274+ )
12391275 if not by_type :
1240- body = Text ("No launches observed yet.\n (Leave Agent Pulse running.)" , style = "#8D99AE" )
1241- return Panel (body , border_style = "#7CFF6B" , title = "[bold #FFD166]◆ AGENT BREAKDOWN[/]" )
1276+ body = Text ("No sub-agent launches observed yet.\n (Leave Agent Pulse running.)" , style = "#8D99AE" )
1277+ return Panel (body , border_style = "#7CFF6B" , title = title )
12421278
1243- total = max (sum ( by_type . values ()) , 1 )
1279+ total = max (total_24h , 1 )
12441280 maxv = max (by_type .values ())
12451281 type_icons = {
12461282 "explore" : "⚡" , "task" : "⚙" , "general-purpose" : "●" ,
@@ -1271,7 +1307,7 @@ def bar(n: int, width: int = 16) -> Text:
12711307 bar (by_type [k ]).stylize (color ),
12721308 )
12731309
1274- return Panel (table , border_style = "#7CFF6B" , title = "[bold #FFD166]◆ AGENT BREAKDOWN[/]" )
1310+ return Panel (table , border_style = "#7CFF6B" , title = title )
12751311
12761312
12771313class SignalPanel (Static ):
@@ -1509,6 +1545,14 @@ def render(self) -> Panel:
15091545 Text ("Errors (24h)" , style = "#8D99AE" ),
15101546 Text (str (m .error_count_24h ), style = err_color ),
15111547 )
1548+ t .add_row (
1549+ Text ("Sub-Agents running" , style = "#8D99AE" ),
1550+ Text (str (m .running_subagents ), style = "bold #B388FF" ),
1551+ )
1552+ t .add_row (
1553+ Text ("Sub-Agents (24h)" , style = "#8D99AE" ),
1554+ Text (str (m .spawned_today ), style = "bold #7CFF6B" ),
1555+ )
15121556
15131557 pulse = "●" if self .tick % 2 == 0 else "○"
15141558 status = Text .assemble ((pulse + " " , f"bold { color } " ), ("MONITORING" , f"bold { color } " ))
0 commit comments