Skip to content

feat(go): support custom tools in subagents#962

Open
ductrung-nguyen wants to merge 1 commit intogithub:mainfrom
ductrung-nguyen:feat/support-custom-tools-subagent
Open

feat(go): support custom tools in subagents#962
ductrung-nguyen wants to merge 1 commit intogithub:mainfrom
ductrung-nguyen:feat/support-custom-tools-subagent

Conversation

@ductrung-nguyen
Copy link
Copy Markdown

All three SDKs (Go, Python, Node) use per-session tool handler lookup keyed to the exact session ID. When the CLI creates child sessions for subagents, those child session IDs are never registered in the SDK's sessions map. Tool calls arriving with a child session ID fail with "unknown session <id>".

This PR only brings the ability to call custom tools for sub-agent in Go, by following the proposal in #947

@ductrung-nguyen ductrung-nguyen requested a review from a team as a code owner March 31, 2026 08:22
Copilot AI review requested due to automatic review settings March 31, 2026 08:22
Copy link
Copy Markdown
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 enables Go SDK sessions to handle custom tool calls originating from CLI-created subagent (child) sessions by resolving child session IDs back to the parent session and enforcing per-agent tool allowlists.

Changes:

  • Track child→parent (and child→agent) session lineage from subagent.* lifecycle events and resolve incoming RPC requests against the parent session.
  • Enforce CustomAgentConfig.Tools allowlists for tool calls coming from child sessions.
  • Add Go docs plus unit/integration tests and placeholder replay snapshots for future E2E capture.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
go/client.go Adds child-session lineage tracking, resolveSession, and allowlist enforcement for child-originated tool calls; updates request handlers to resolve via parent.
go/session.go Stores custom agent configs on the session and adds an onDestroy callback for client-side cleanup.
go/client_subagent_test.go Adds unit tests covering session resolution, allowlists, subagent tracking, cleanup, and concurrency safety.
go/internal/e2e/subagent_tool_test.go Adds integration-tagged tests exercising real CLI subagent tool invocation and deny behavior.
go/README.md Documents subagent custom tool routing and tool access control semantics.
test/snapshots/subagent_tool/subagent_invokes_parent_custom_tool.yaml Placeholder snapshot intended to be captured from a real CLI session for subagent custom tool flow.
test/snapshots/subagent_tool/subagent_denied_unlisted_tool_returns_unsupported.yaml Placeholder snapshot intended to be captured from a real CLI session for denied-tool flow.
Comments suppressed due to low confidence (3)

go/client.go:1706

  • Now that child session IDs are resolved to the parent session via resolveSession, the user-input handler invoked via session.handleUserInputRequest(...) will receive UserInputInvocation.SessionID == parentSessionID (because the invocation is built inside Session). This loses the child session context for subagent-originated prompts. Consider plumbing the original req.SessionID through so handlers can distinguish parent vs child/subagent requests.
	session, _, err := c.resolveSession(req.SessionID)
	if err != nil {
		return nil, &jsonrpc2.Error{Code: -32602, Message: err.Error()}
	}

	response, err := session.handleUserInputRequest(UserInputRequest{
		Question:      req.Question,
		Choices:       req.Choices,
		AllowFreeform: req.AllowFreeform,
	})

go/client.go:1726

  • Similar to user input: after resolving a child session to the parent, session.handleHooksInvoke(...) will build HookInvocation.SessionID from the parent session ID. That makes hook handlers unable to attribute hook invocations to the originating child/subagent session. Consider passing the original req.SessionID through (or extending the invocation type) so hook consumers can enforce policy per subagent session when needed.
	session, _, err := c.resolveSession(req.SessionID)
	if err != nil {
		return nil, &jsonrpc2.Error{Code: -32602, Message: err.Error()}
	}

	output, err := session.handleHooksInvoke(req.Type, req.Input)
	if err != nil {

go/client.go:1853

  • After resolveSession enables permission requests from child sessions, PermissionInvocation.SessionID is still populated from the resolved parent session (session.SessionID). That prevents permission handlers from knowing which child/subagent session requested the permission. Consider using the original req.SessionID (and/or adding ParentSessionID) so permission handlers can implement subagent-specific policy.
	session, _, err := c.resolveSession(req.SessionID)
	if err != nil {
		return nil, &jsonrpc2.Error{Code: -32602, Message: err.Error()}
	}

	handler := session.getPermissionHandler()
	if handler == nil {

All three SDKs (Go, Python, Node) use per-session tool handler lookup keyed
to the exact session ID. When the CLI creates child sessions for subagents,
those child session IDs are never registered in the SDK's sessions map.
Tool calls arriving with a child session ID fail with "unknown session".

This PR adds the ability to call custom tools for sub-agents in Go,
following the proposal in github#947
@ductrung-nguyen ductrung-nguyen force-pushed the feat/support-custom-tools-subagent branch from 524cf68 to 0b6a41d Compare April 1, 2026 11:11
@ductrung-nguyen ductrung-nguyen changed the title feat: support custom tools in subagents feat(go): support custom tools in subagents Apr 1, 2026
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