feat: subagent activity viewing#1513
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end support for viewing subagent activity inline within the parent chat UI, and propagates user/source context through the A2A request chain so subagent sessions are stored and fetched correctly.
Changes:
- UI: propagate
subagent_session_idthrough tool call/result metadata and render an inline “Activity” panel that polls the subagent session’s tasks. - Python (ADK/Core): forward
x-user-idandx-kagent-sourceheaders for subagent calls; stamp subagent session ids into A2A DataPart metadata. - Go: persist a
Session.Sourcefield and filter subagent sessions out of agent session listings.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/lib/messageHandlers.ts | Adds subagent_session_id extraction/propagation into processed tool call/result metadata. |
| ui/src/components/chat/ToolCallDisplay.tsx | Threads subagent session id into AgentCallDisplay for agent-tool calls. |
| ui/src/components/chat/AgentCallDisplay.tsx | Implements the expandable “Activity” panel with polling + nested chat rendering and depth limiting. |
| ui/src/app/actions/sessions.ts | Adds a helper to fetch subagent session + tasks for polling. |
| python/packages/kagent-core/src/kagent/core/a2a/_requests.py | Extracts forwarded user id and propagates subagent source tag via request context state. |
| python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py | Stamps subagent_session_id into function_call DataPart metadata when provided. |
| python/packages/kagent-adk/src/kagent/adk/_session_service.py | Includes source in session creation payload. |
| python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py | Adds request interceptors for user/source headers; exposes a subagent session id in tool output. |
| python/packages/kagent-adk/src/kagent/adk/_agent_executor.py | Threads subagent session id mapping into the event converter; propagates source into session creation state. |
| go/core/internal/httpserver/handlers/sessions.go | Accepts and persists source from SessionRequest. |
| go/core/internal/database/fake/client.go | Filters out subagent sessions in agent session listing (fake DB). |
| go/core/internal/database/client.go | Filters out subagent sessions in agent session listing (real DB). |
| go/api/httpapi/types.go | Adds source to SessionRequest type. |
| go/api/database/models.go | Adds Source column to Session model. |
| docs/architecture/subagent-viewing.md | Documents the feature’s request/data flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| # Pre-generate context_id for UI session polling | ||
| self._last_context_id: str = str(uuid.uuid4()) | ||
|
|
||
| @property | ||
| def subagent_session_id(self) -> str | None: | ||
| """The subagent's session ID (== context_id sent in the A2A message).""" | ||
| return self._last_context_id |
There was a problem hiding this comment.
Tool instances are created fresh per request and destroyed after, so this _last_context_id UUID is effectively per-request already. For more info see the following comment, these three comments are all related to each other.
| @@ -596,7 +604,11 @@ async def _handle_request( | |||
| run_metadata[get_kagent_metadata_key("invocation_id")] = real_invocation_id | |||
|
|
|||
| for a2a_event in convert_event_to_a2a_events( | |||
| adk_event, invocation_context, context.task_id, context.context_id | |||
| adk_event, | |||
| invocation_context, | |||
| context.task_id, | |||
| context.context_id, | |||
| subagent_session_ids=subagent_session_ids or None, | |||
| ): | |||
There was a problem hiding this comment.
This sounds like an edge case, but when combined with the behaviour of the above comment, it's actually an intended feature. So the behaviour overall is:
User -> Main agent -> Subagent (multiple / parallel / sequential) -> they go into the same session for that subagent, the UI shows the same session -> each subagent return a response (existing behaviour) -> main agent
Now when user does another invocation of main agent:
User -> Main agent -> Runner gets recreated (existing behaviour) -> KagentRemoteA2ATool gets reset + session ID reset -> subagent (new session) -> ...
Because within each invocation the session ID for the same subagent is the same, therefore stamping the data part with the same ID mapped by name is not a problem. 😄
This is intended because when the main agent invokes the subagent sequentially, it's because it want to continue the conversation and the flow here is designed to support this.
| def _process_subagent_session_id(a2a_part: A2APart, subagent_session_ids: Dict[str, str]) -> None: | ||
| """Stamps a subagent session ID onto a function_call DataPart. | ||
|
|
||
| If the part is a function_call whose tool name appears in | ||
| ``subagent_session_ids``, the corresponding session ID is added to | ||
| the DataPart metadata so the UI can find the subagent session. | ||
|
|
||
| Args: | ||
| a2a_part: The A2A part to potentially stamp. | ||
| subagent_session_ids: Mapping of tool name to pre-generated session ID. | ||
| """ | ||
| if not isinstance(a2a_part.root, DataPart) or not a2a_part.root.metadata: | ||
| return | ||
| if ( | ||
| a2a_part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)) | ||
| != A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL | ||
| ): | ||
| return | ||
| tool_name = a2a_part.root.data.get("name") if isinstance(a2a_part.root.data, dict) else None | ||
| if tool_name and tool_name in subagent_session_ids: | ||
| a2a_part.root.metadata[get_kagent_metadata_key("subagent_session_id")] = subagent_session_ids[tool_name] |
There was a problem hiding this comment.
See above comment
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io> userid propagation to subagents Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io> subagents Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io> You are currently rebasing branch 'jetc/feat/subagent-viewing' on '802f95be'.
21df4cb to
f963623
Compare
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>

Allows for viewing activity of subagents in the chat + fixes like user id passthrough for subagents