Skip to content

Commit 5a48cc7

Browse files
Gregg CochranCopilot
andcommitted
Surface real-time sub-agent counts in Live Metrics, Trends, Breakdown, Fleet
- Add PulseMetrics.running_subagents (agent_events with outcome='unknown' in last 30m, max'd with process-based signal) and subagents_last5m - New PulseStore.running_subagents_since() query - Live Metrics: split 'Agents (sessions)' vs 'Sub-Agents running' vs 'Sub-Agents (5m)' vs 'Sub-Agents today' - Trend Analysis: rename Agents row/sparkline to Sub-Agents; all-time label clarified - Sub-Agent Breakdown: retitled, now shows running count and 24h total in header - Fleet Health: adds Sub-Agents running and Sub-Agents (24h) rows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a16c939 commit 5a48cc7

1 file changed

Lines changed: 61 additions & 17 deletions

File tree

agent_pulse.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

12771313
class 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

Comments
 (0)