Skip to content

Native Subagent Handoff for Agent Trees (Transfer Events + Author Provenance)#532

Open
victorbash400 wants to merge 2 commits intostrands-agents:mainfrom
victorbash400:main
Open

Native Subagent Handoff for Agent Trees (Transfer Events + Author Provenance)#532
victorbash400 wants to merge 2 commits intostrands-agents:mainfrom
victorbash400:main

Conversation

@victorbash400
Copy link

Description

This PR introduces native sub-agent handoff support to the SDK. It adds tree-aware routing (name, subAgents, parentAgent, transfer restrictions), transfer lifecycle events (BeforeTransferEvent, AfterTransferEvent), and message author provenance (Message.author) so a shared timeline can preserve which agent produced each response. It also exports the new events from public entry points and documents usage in the README with a concrete handoff example.

Transfer restrictions are configurable per agent: by default, transfers can target children, parent, and siblings; disallowTransferToParent and disallowTransferToPeers restrict those routes when needed.

Compatibility: this extends AgentStreamEvent with additive variants and keeps existing single-agent behavior intact. Nested sub-agents are intentionally disallowed (one parent level with direct children only), with explicit validation errors.

See README section SubAgents and Handoff for a complete setup example and behavior notes.

Related Issues

N/A

Documentation PR

N/A (README updated in this PR)

Type of Change

New feature

Testing

Added and updated unit tests in src/agent/__tests__/subagents.test.ts to cover parent/child wiring, transfer events, invalid transfer handling, resume behavior, and nested sub-agent rejection. Ran full repo validation with npm run check (lint, format, type-check, coverage, package tests).

Validated end-to-end behavior in downstream app: GWEN, configured with local dependency file:../sdk-typescript-with-subagents, exercising transfer and resume flows through live chat session logs.

  • I ran npm run check

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

- disallow nested sub-agents (child subAgents now throw)
- preserve resume-from-last-agent behavior for root entry
- honor explicit child invoke/stream entry points
- add regression tests for nested-tree rejection and explicit child entry
- update README note for one-level tree support
- run prettier fixes for transfer tool/test files
@github-actions github-actions bot added the strands-running <strands-managed> Whether or not an agent is currently running label Feb 14, 2026
}

child.parentAgent = this
;(child as unknown as { messages: Message[] }).messages = this.messages

Choose a reason for hiding this comment

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

Issue: Casting workaround for readonly property is fragile

This casting (child as unknown as { messages: Message[] }).messages = this.messages circumvents TypeScript's readonly modifier. This pattern is error-prone and makes the shared timeline behavior implicit.

Suggestion: Consider making messages a private property with a public getter, or document this shared timeline behavior more explicitly in the class-level TSDoc. Alternatively, the child could hold a reference to the root agent rather than directly mutating its messages property.

expect(() => new Agent({ name: 'root', model: new MockMessageModel(), subAgents: [childWithNested] })).toThrow(
'Nested sub-agents are not supported'
)
})

Choose a reason for hiding this comment

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

Issue: Missing test coverage for transfer restriction options

Tests exist for the basic handoff flow but missing coverage for disallowTransferToParent and disallowTransferToPeers configuration options.

Suggestion: Add tests verifying:

  1. Child with disallowTransferToParent: true cannot transfer back to parent
  2. Child with disallowTransferToPeers: true cannot transfer to siblings
  3. The MAX_CONSECUTIVE_TRANSFERS guard is triggered and handled correctly

} from '../hooks/events.js'
import { createTransferToAgentTool } from '../tools/transfer-to-agent-tool.js'

const MAX_CONSECUTIVE_TRANSFERS = 8

Choose a reason for hiding this comment

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

Issue: Magic number without configuration option

MAX_CONSECUTIVE_TRANSFERS = 8 is a hard-coded limit. Users might have valid use cases requiring more or fewer consecutive transfers.

Suggestion: Consider making this configurable via AgentConfig with a sensible default:

maxConsecutiveTransfers?: number  // defaults to 8

this.fromAgentName = data.fromAgentName
this.toAgentName = data.toAgentName
}
}

Choose a reason for hiding this comment

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

Issue: AfterTransferEvent should override _shouldReverseCallbacks() for consistency

All other "After" events (AfterInvocationEvent, AfterToolCallEvent, AfterModelCallEvent, AfterToolsEvent) override _shouldReverseCallbacks() to return true for proper cleanup semantics. AfterTransferEvent should follow the same pattern.

Suggestion:

override _shouldReverseCallbacks(): boolean {
  return true
}

@github-actions
Copy link

Issue: This PR introduces significant public API surface and should be labeled for API review

Per the API Bar Raising guidelines, PRs that add new public classes/abstractions or primitives that customers will frequently use require the needs-api-review label. This PR adds:

  • New AgentConfig properties: name, subAgents, parentAgent, disallowTransferToParent, disallowTransferToPeers
  • New Message.author property
  • New events: BeforeTransferEvent, AfterTransferEvent
  • New Agent methods: rootAgent, findAgent(), findSubAgent()

Suggestion: Add the needs-api-review label and ensure the PR description includes:

  1. Complete API signatures with default parameter values
  2. Example code snippets demonstrating all use cases
  3. Module exports affected

```

Notes:
- Agent names are required for agents in a sub-agent tree.

Choose a reason for hiding this comment

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

Issue: README example doesn't demonstrate all new configuration options

The documentation shows basic usage but doesn't cover disallowTransferToParent, disallowTransferToPeers, or the Message.author field that users can inspect.

Suggestion: Consider adding a brief note about these options:

// Example showing transfer restrictions
const mathAgent = new Agent({
  name: 'math',
  model,
  systemPrompt: 'You are a math specialist.',
  disallowTransferToParent: false,  // default: can transfer back to parent
  disallowTransferToPeers: false,   // default: can transfer to sibling agents
})

// Accessing message author after handoff
for (const message of root.messages) {
  console.log(`${message.author ?? 'unknown'}: ${message.content}`)
}

@github-actions
Copy link

Review Summary

Assessment: Request Changes

This PR introduces a well-designed sub-agent handoff system with clear tree-aware routing and lifecycle events. The overall architecture is solid and follows existing SDK patterns.

Key Review Themes
  • API Review Process: PR needs needs-api-review label given the substantial public API surface being added
  • Consistency: AfterTransferEvent should follow the pattern of other "After" events for callback ordering
  • Test Coverage: Missing tests for transfer restriction options (disallowTransferToParent, disallowTransferToPeers)
  • Documentation: README could better showcase all configuration options and the Message.author field

The implementation is thoughtful and the shared timeline approach via message provenance is a clean solution. Looking forward to seeing this feature land with the suggested improvements!

@github-actions github-actions bot removed the strands-running <strands-managed> Whether or not an agent is currently running label Feb 14, 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.

1 participant