Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions cargento/skills/cargento/cargento_runtime/collectors/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def collect(
tasks_by_session = load_tasks(config)
team_members = load_team_members(config)
transcripts: dict[str, tuple[str, float]] = {} # prefix -> (newest path, its mtime)
agent_children: dict[str, list[dict[str, Any]]] = {} # parent prefix -> children
raw_children: list[dict[str, Any]] = []
for fp in runtime_io.glob_stores(config, "claude.projects", "*", "*.jsonl"):
base = os.path.basename(fp)
if "-agent-" in base or base.startswith("agent-"):
Expand All @@ -488,15 +488,17 @@ def collect(
if parent_prefix and runtime_sessions.is_fresh(
config, now, mtime, window_hours * 3600
):
agent_children.setdefault(parent_prefix, []).append(
raw_children.append(
{
"path": fp,
"prefix": base[:8],
"mtime": mtime,
# Both unbounded: `published_agent` redacts and
# then bounds for display, and the roster join
# needs the name as written or a long one misses.
"label": agent_name or "subagent",
"agent_name": agent_name,
"parent_prefix": parent_prefix,
}
)
continue
Expand All @@ -508,6 +510,35 @@ def collect(
if prefix not in transcripts or mtime > transcripts[prefix][1]:
transcripts[prefix] = (fp, mtime)

# DRC-4347. Resolve nested teammate parentage.
# In Claude Code, the harness coordinates agent teams under the team
# coordinator's identifier `teamName: session-<lead-prefix>`, which is
# passed to every spawned teammate via `--team-name`. Real harness
# teammate transcripts therefore naturally bucket under the lead session.
# If a transcript points to an intermediate child prefix rather than a
# top-level session prefix (e.g. nested team dispatches or synthetic test
# structures), trace the parent chain to the root session so the nested
# child is not silently discarded, and attribute `parent` to the
# intermediate teammate's label.
agent_children: dict[str, list[dict[str, Any]]] = {}
children_by_prefix: dict[str, dict[str, Any]] = {c["prefix"]: c for c in raw_children}
for c in raw_children:
parent_pref = c["parent_prefix"]
immediate_parent_label: str | None = None
seen = {c["prefix"]}
curr = parent_pref
while curr in children_by_prefix and curr not in transcripts:
if immediate_parent_label is None:
immediate_parent_label = children_by_prefix[curr]["label"]
seen.add(curr)
next_pref = children_by_prefix[curr]["parent_prefix"]
if not next_pref or next_pref in seen:
break
curr = next_pref
root_prefix = curr
c["parent"] = immediate_parent_label
agent_children.setdefault(root_prefix, []).append(c)

out: list[Session] = []
for prefix in set(transcripts) | set(tasks_by_session):
newest = transcripts.get(prefix)
Expand Down Expand Up @@ -689,7 +720,7 @@ def collect(
model=models.get(c["path"]),
started_at=child_started[c["path"]],
active=child_is_live[c["path"]],
parent=None,
parent=c.get("parent"),
)
)
# A teammate's own subagents, flattened onto the roster rather than
Expand Down
69 changes: 68 additions & 1 deletion cargento/skills/cargento/tests/test_claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -2938,6 +2938,7 @@ def teammate(
age: float,
now: float,
preamble: bool = False,
parent_prefix: str | None = None,
) -> Path:
"""One classified top-level teammate transcript.

Expand All @@ -2953,13 +2954,14 @@ def teammate(
json.dumps({"type": "mode", "mode": "default"}),
json.dumps({"type": "permission-mode", "permissionMode": "acceptEdits"}),
]
pref = parent_prefix if parent_prefix is not None else self.PARENT[:8]
lines.append(
json.dumps(
{
"type": "user",
"sessionId": sid,
"agentName": name,
"teamName": f"session-{self.PARENT[:8]}",
"teamName": f"session-{pref}",
"timestamp": stamp,
"message": {"role": "user", "content": "do the work"},
}
Expand Down Expand Up @@ -3145,6 +3147,71 @@ def test_a_teammates_own_subagents_are_published_under_it(self) -> None:
# lead is running one teammate, whatever that teammate is running.
self.assertEqual("running 1 subagent", session["state_detail"])

def test_nested_teammate_with_child_parent_prefix_is_published_under_lead(self) -> None:
# DRC-4347 AC-1, AC-2, AC-3. A teammate whose transcript specifies a
# teamName pointing to an intermediate child rather than the lead
# session is resolved to the lead session and published under it,
# with parent set to the intermediate teammate, and its own subagents
# flattened beneath it.
now = time.time()
stamp = datetime.fromtimestamp(now - 60, UTC).isoformat()
nested_stamp = datetime.fromtimestamp(now - 50, UTC).isoformat()
worker_stamp = datetime.fromtimestamp(now - 40, UTC).isoformat()
child_sid = "bbbb2222-0000-0000-0000-000000000000"
nested_sid = "cccc3333-0000-0000-0000-000000000000"
with tempfile.TemporaryDirectory() as tmp:
proj = self.project(tmp, now=now)
# Direct teammate of lead (parent = aaaa1111)
self.teammate(proj, sid=child_sid, name="ensign-review", stamp=stamp, age=5, now=now)
# Nested teammate dispatched by ensign-review (parent = bbbb2222)
self.teammate(
proj,
sid=nested_sid,
name="ensign-nested",
stamp=nested_stamp,
age=4,
now=now,
parent_prefix=child_sid[:8],
)
# Nested teammate's own workers
workers = proj / nested_sid / "subagents"
workers.mkdir(parents=True)
wfp = workers / "agent-worker.jsonl"
wfp.write_text(
json.dumps({"type": "user", "timestamp": worker_stamp, "message": {}}) + "\n"
)
(workers / "agent-worker.meta.json").write_text(json.dumps({"name": "worker-lens"}))
os.utime(wfp, (now - 10, now - 10))

session = self.collect_one(tmp, None, now)

published = {a["name"]: a for a in session["subagents"]}
self.assertEqual({"ensign-review", "ensign-nested", "worker-lens"}, set(published))
self.assertIsNone(published["ensign-review"]["parent"])
self.assertEqual("ensign-review", published["ensign-nested"]["parent"])
self.assertEqual("ensign-nested", published["worker-lens"]["parent"])
self.assertEqual(records.parse_ts(nested_stamp), published["ensign-nested"]["started_at"])
self.assertIs(True, published["ensign-nested"]["active"])

def test_teammates_sharing_harness_lead_team_name_are_published_under_lead(self) -> None:
# DRC-4347 AC-4. Claude Code coordinates agent teams under the team
# coordinator's identifier `teamName: session-<lead-prefix>`. Multiple
# teammates sharing this teamName all bucket under the lead session.
now = time.time()
stamp = datetime.fromtimestamp(now - 60, UTC).isoformat()
t1_sid = "bbbb2222-0000-0000-0000-000000000000"
t2_sid = "cccc3333-0000-0000-0000-000000000000"
with tempfile.TemporaryDirectory() as tmp:
proj = self.project(tmp, now=now)
self.teammate(proj, sid=t1_sid, name="ensign-review", stamp=stamp, age=5, now=now)
self.teammate(proj, sid=t2_sid, name="ensign-build", stamp=stamp, age=4, now=now)
session = self.collect_one(tmp, None, now)

published = {a["name"]: a for a in session["subagents"]}
self.assertEqual({"ensign-review", "ensign-build"}, set(published))
self.assertIsNone(published["ensign-review"]["parent"])
self.assertIsNone(published["ensign-build"]["parent"])

def test_a_quiet_agent_the_lead_dispatched_itself_stays_published(self) -> None:
# captain-ruling[2026-09-03]. The window gate reached children and
# grandchildren and left the lead's OWN agents fresh-gated at 90 s, so a
Expand Down
Loading