diff --git a/CLAUDE.md b/CLAUDE.md index 6fc149803..75e671242 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -680,7 +680,7 @@ Every event on `/v1/sessions/{id}` and `/v1/events` carries: - `dialAddr` — the address the egress guard checked and the proxy dialed, `"ip:port"`, on a tunnel's open row when `listener.egress_guard` is on. With `host` it is the audit trail against DNS rebinding: under the guard the proxy resolves once, checks that answer, and dials exactly it, so the two fields together say "asked for the name, reached this address". Absent when the guard is off and on rows that dialed nothing. - `httpMethod`, `httpPath` — the HTTP verb and path, so a request no parser recognized is still identifiable rather than showing only a host. Distinct from the `method` inside `a2a` / `mcp`, which is a protocol method name. On an opaque tunnel `httpMethod` is `CONNECT` and `httpPath` is absent — opaque bytes carry no request line. The path is query-stripped and percent-decoded, so query-borne credentials never reach the timeline, but a secret in a path *segment* (a bot token, a webhook path) does survive on this unauthenticated surface — worth knowing before exporting events off-box. - `subagent` — present on rows from a Claude Code subagent or agent-team teammate: the `X-Claude-Code-Agent-Id` the request carried. For a subagent that is also the `` in `~/.claude/projects///subagents/agent-.meta.json`; for a teammate the join is unverified. Resolved by `Context.Subagent`, pinned with the User-Agent by `ResolveClient` so no plugin can re-file a row, and **refused rather than sanitised** when unusable (control runes, over 128 bytes), because it is a join key and a cleaned-up id joins nothing. It does not move the row: a subagent's requests stay in their parent's session, so the header must not be added to `session.id_headers`. **It names an agent, it does not classify the row:** `inference.agentRole` stays the field that says whether a row is a subagent's, and a teammate's row is `agentRole: main` with `subagent` set — Claude Code writes `cc_is_subagent=true` only for `agentType: subagent`. -- `parentSubagent` — the agent that started `subagent`, from `X-Claude-Code-Parent-Agent-Id`, resolved with it in one memo (`Context.ParentSubagent`) and under the same rules. Claude Code sends it only when the spawner was itself an agent, so **absent on a row that names a `subagent` means the conversation started it**, not "unknown". The two keys are the tree's node and its edge. +- `parentSubagent` — the agent that started `subagent`, from `X-Claude-Code-Parent-Agent-Id`, resolved with it in one memo (`Context.ParentSubagent`) and under the same rules. Claude Code sends it only when the spawner was itself an agent, so **absent on a row that names a `subagent` means the conversation started it**, not "unknown". The two keys are the tree's node and its edge. agentop's events pane groups each subagent's rows under a collapsible header anchored at its first row (#1047): the label is the subagent's description from the harvested `agent-.meta.json` (`SessionMetadata.Subagents`), falling back to the raw id; blocks are collapsed by default, `Enter` toggles, a sort disables grouping, and a live search opens every block. ### Invocation action vocabulary diff --git a/cmd/agentop/README.md b/cmd/agentop/README.md index 5dd9c310b..d227c7a17 100644 --- a/cmd/agentop/README.md +++ b/cmd/agentop/README.md @@ -1123,6 +1123,18 @@ agentop is for, and the other four are surfaces you visit and leave. the same as a body of zero bytes: a body no plugin asked to buffer is relayed unmeasured and reads exactly as a body-less GET does. + A session with subagent traffic groups each subagent's rows under a + collapsible header at the subagent's first row: `▸─