Skip to content

feat: subagent activity viewing#1513

Open
supreme-gg-gg wants to merge 8 commits intokagent-dev:mainfrom
supreme-gg-gg:jetc/feat/subagent-viewing
Open

feat: subagent activity viewing#1513
supreme-gg-gg wants to merge 8 commits intokagent-dev:mainfrom
supreme-gg-gg:jetc/feat/subagent-viewing

Conversation

@supreme-gg-gg
Copy link
Contributor

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

Copilot AI review requested due to automatic review settings March 17, 2026 05:30
@supreme-gg-gg
Copy link
Contributor Author

In action:

Screen.Recording.2026-03-16.at.4.12.53.PM.mov

How nested agent calls look like (note I set a max depth of 3, there is no theoretical max depth, but the UI looks really bad and it's not practical to have more than 3 layers of subagent calls...)

Screenshot 2026-03-17 at 1 21 02 AM

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_id through tool call/result metadata and render an inline “Activity” panel that polls the subagent session’s tasks.
  • Python (ADK/Core): forward x-user-id and x-kagent-source headers for subagent calls; stamp subagent session ids into A2A DataPart metadata.
  • Go: persist a Session.Source field 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.

Comment on lines +135 to +141
# 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
Copy link
Contributor Author

@supreme-gg-gg supreme-gg-gg Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 590 to 612
@@ -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,
):
Copy link
Contributor Author

@supreme-gg-gg supreme-gg-gg Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +137 to +157
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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'.
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
@supreme-gg-gg supreme-gg-gg force-pushed the jetc/feat/subagent-viewing branch from 21df4cb to f963623 Compare March 17, 2026 17:03
Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants