Skip to content

Commit 98cf603

Browse files
SEA CAPITALclaude
authored andcommitted
feat: Subagent grouping — hide from agents list, group by session
New /api/session-records endpoint: Returns main + subagent records grouped by session_id Main and sub records share session_id (already linked in data) /api/agents changes: Subagent agents (name contains /subagent) hidden from list Subagent record counts merged into parent agent ?show_subagents=1 to override This makes subagents a "part of" the main agent's work, not independent agents. Like an employee's expense report nesting their taxi rides — not listing taxis as employees. 849 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4b38178 commit 98cf603

2 files changed

Lines changed: 61 additions & 3 deletions

File tree

sdk/python/atlast_ecp/dashboard_server.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,48 @@ def _resolve_agent_name(self, name: str) -> str:
105105
def _dispatch_api(self, path: str, params: dict) -> dict:
106106
from .query import search, trace, audit, timeline, rebuild_index, list_agents, list_threads, get_thread
107107

108+
# ── Session Records (main + subagents grouped) ──
109+
if path == "/api/session-records":
110+
session_id = params.get("session", [""])[0]
111+
if not session_id:
112+
return {"error": "session parameter required"}
113+
from .query import _ensure_index as _sri, _get_db as _srdb
114+
_sri()
115+
db_sr = _srdb()
116+
rows = db_sr.execute(
117+
"SELECT id, agent, ts, step_type, action, model, latency_ms, "
118+
"tokens_in, tokens_out, input_preview, output_preview, error, is_infra, flags "
119+
"FROM records WHERE session_id = ? OR thread_id = ? ORDER BY ts ASC",
120+
(session_id, session_id)
121+
).fetchall()
122+
db_sr.close()
123+
main_records = []
124+
sub_records = []
125+
for r in rows:
126+
rec = {
127+
"id": r[0], "agent": r[1], "ts": r[2], "step_type": r[3],
128+
"action": r[4], "model": r[5], "latency_ms": r[6],
129+
"tokens_in": r[7], "tokens_out": r[8],
130+
"input_preview": r[9], "output_preview": r[10],
131+
"error": r[11], "is_infra": r[12], "flags": r[13],
132+
"is_subagent": "/subagent" in (r[1] or ""),
133+
}
134+
if rec["is_subagent"]:
135+
sub_records.append(rec)
136+
else:
137+
main_records.append(rec)
138+
return {
139+
"session_id": session_id,
140+
"main_records": main_records,
141+
"sub_records": sub_records,
142+
"total_main": len(main_records),
143+
"total_sub": len(sub_records),
144+
"total_tokens_in": sum(r.get("tokens_in") or 0 for r in main_records + sub_records),
145+
"total_tokens_out": sum(r.get("tokens_out") or 0 for r in main_records + sub_records),
146+
}
147+
108148
# ── Token Analytics ──
109-
if path == "/api/token-stats":
149+
elif path == "/api/token-stats":
110150
from .query import _ensure_index as _tei, _get_db as _tdb
111151
_tei()
112152
db_t = _tdb()
@@ -278,13 +318,31 @@ def _dispatch_api(self, path: str, params: dict) -> dict:
278318
# ── Agents: list all agents with stats ──
279319
elif path == "/api/agents":
280320
agents = list_agents(as_json=True)
281-
# Inject friendly names: keep DID in agent_did, show name in agent
321+
# Inject friendly names
282322
for a in agents:
283323
did = a.get("agent", "")
284324
name = a.get("agent_name", "")
285325
if name and name != did:
286326
a["agent_did"] = did
287327
a["agent"] = name
328+
a["is_subagent"] = "/subagent" in (a.get("agent_name") or "") or "/sub" in (a.get("agent_name") or "")
329+
330+
# Merge subagent counts into main agents and filter
331+
sub_counts = {}
332+
for a in agents:
333+
if a.get("is_subagent"):
334+
# "foo/subagent" → main = "foo"
335+
main_name = (a.get("agent_name") or "").split("/")[0]
336+
sub_counts[main_name] = sub_counts.get(main_name, 0) + a.get("total_records", 0)
337+
for a in agents:
338+
name = a.get("agent_name") or ""
339+
if name in sub_counts:
340+
a["subagent_records"] = sub_counts[name]
341+
342+
# Filter: hide subagents from main list (they're shown under main agent)
343+
show_sub = params.get("show_subagents", [""])[0] == "1"
344+
if not show_sub:
345+
agents = [a for a in agents if not a.get("is_subagent")]
288346
# Compute per-agent Trust Score
289347
try:
290348
from .scoring_rules import classify_records, compute_trust_score_1000

sdk/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "atlast-ecp"
7-
version = "0.30.1"
7+
version = "0.30.2"
88
description = "ATLAST Evidence Chain Protocol — trust infrastructure for AI Agents"
99
readme = "README.md"
1010
license = { text = "MIT" }

0 commit comments

Comments
 (0)