-
Notifications
You must be signed in to change notification settings - Fork 27
feat(agent): add tool-output and V2 escalation recipient types [ACTN-10480] #1667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,18 +22,29 @@ class TaskRecipientType(str, enum.Enum): | |
| GROUP_ID = "GroupId" | ||
| EMAIL = "UserEmail" | ||
| GROUP_NAME = "GroupName" | ||
| WORKLOAD = "Workload" | ||
| ROUND_ROBIN = "RoundRobin" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should CustomAssignments be required as an option here? |
||
|
|
||
|
|
||
| class TaskRecipient(BaseModel): | ||
| """Model representing a task recipient.""" | ||
| """Model representing a task recipient. | ||
|
|
||
| `value` is the single identifier (group name, group id, user id, email, …). | ||
| `values` is the multi-assignee form used by Workload-with-custom-emails | ||
| assignments; when set it takes precedence over `value` for the | ||
| `assigneeNamesOrEmails` payload. | ||
| """ | ||
|
|
||
| type: Literal[ | ||
| TaskRecipientType.USER_ID, | ||
| TaskRecipientType.GROUP_ID, | ||
| TaskRecipientType.EMAIL, | ||
| TaskRecipientType.GROUP_NAME, | ||
| TaskRecipientType.WORKLOAD, | ||
| TaskRecipientType.ROUND_ROBIN, | ||
| ] = Field(..., alias="type") | ||
| value: str = Field(..., alias="value") | ||
| values: Optional[List[str]] = Field(default=None, alias="values") | ||
| display_name: Optional[str] = Field(default=None, alias="displayName") | ||
|
|
||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,6 +140,9 @@ class AgentEscalationRecipientType(str, CaseInsensitiveEnum): | |
| ASSET_GROUP_NAME = "AssetGroupName" | ||
| ARGUMENT_EMAIL = "ArgumentEmail" | ||
| ARGUMENT_GROUP_NAME = "ArgumentGroupName" | ||
| WORKLOAD = "Workload" | ||
| ROUND_ROBIN = "RoundRobin" | ||
| CUSTOM_ASSIGNEES = "CustomAssignees" | ||
|
|
||
|
|
||
| class AgentContextRetrievalMode(str, CaseInsensitiveEnum): | ||
|
|
@@ -540,6 +543,9 @@ class AgentA2aResourceConfig(BaseAgentResourceConfig): | |
| 6: AgentEscalationRecipientType.ASSET_GROUP_NAME, | ||
| 7: AgentEscalationRecipientType.ARGUMENT_EMAIL, | ||
| 8: AgentEscalationRecipientType.ARGUMENT_GROUP_NAME, | ||
| 9: AgentEscalationRecipientType.WORKLOAD, | ||
| 10: AgentEscalationRecipientType.ROUND_ROBIN, | ||
| 11: AgentEscalationRecipientType.CUSTOM_ASSIGNEES, | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -619,14 +625,86 @@ class ArgumentGroupNameRecipient(BaseEscalationRecipient): | |
| argument_path: str = Field(..., alias="argumentName") | ||
|
|
||
|
|
||
| class WorkloadRecipient(BaseEscalationRecipient): | ||
| """Workload-based group assignment. | ||
|
|
||
| The Action Center distributes tasks to the group member with the lightest workload. | ||
| """ | ||
|
|
||
| type: Literal[AgentEscalationRecipientType.WORKLOAD,] = Field(..., alias="type") | ||
| value: str = Field(..., alias="value") | ||
| display_name: str = Field(..., alias="displayName") | ||
|
|
||
|
|
||
| class RoundRobinRecipient(BaseEscalationRecipient): | ||
| """Round-robin group assignment. | ||
|
|
||
| The Action Center cycles through group members in order on each new task. | ||
| """ | ||
|
|
||
| type: Literal[AgentEscalationRecipientType.ROUND_ROBIN,] = Field(..., alias="type") | ||
| value: str = Field(..., alias="value") | ||
| display_name: str = Field(..., alias="displayName") | ||
|
|
||
|
|
||
| class CustomAssigneesRecipient(BaseEscalationRecipient): | ||
| """Custom multi-user assignment. | ||
|
|
||
| A channel can carry multiple instances, one per assignee email. All are passed | ||
| to Action Center together using a Workload assignment criteria. | ||
| """ | ||
|
|
||
| type: Literal[AgentEscalationRecipientType.CUSTOM_ASSIGNEES,] = Field( | ||
| ..., alias="type" | ||
| ) | ||
| value: str = Field(..., alias="value") | ||
| display_name: Optional[str] = Field(default=None, alias="displayName") | ||
|
|
||
|
|
||
| class ToolOutputRecipient(BaseEscalationRecipient): | ||
| """Recipient whose value is resolved at runtime from a named tool's output. | ||
|
|
||
| Instead of a literal value entered at design time, this binding points at a | ||
| field within a named tool's output. The runtime walks the agent's message | ||
| history, finds the most recent ToolMessage matching `tool_name`, parses its | ||
| content as JSON, and extracts `output_path` (a top-level field for v1). | ||
|
|
||
| Only the assignment-criteria recipient types that accept a runtime-computed | ||
| value are supported: USER_ID, GROUP_ID, WORKLOAD, ROUND_ROBIN, | ||
| CUSTOM_ASSIGNEES. The asset/static/argument types do not participate in | ||
| tool-output binding (they have their own design-time resolution rules). | ||
| """ | ||
|
|
||
| type: Literal[ | ||
| AgentEscalationRecipientType.USER_ID, | ||
| AgentEscalationRecipientType.GROUP_ID, | ||
| AgentEscalationRecipientType.WORKLOAD, | ||
| AgentEscalationRecipientType.ROUND_ROBIN, | ||
| AgentEscalationRecipientType.CUSTOM_ASSIGNEES, | ||
| ] = Field(..., alias="type") | ||
| source: Literal["toolOutput"] = Field(..., alias="source") | ||
| tool_name: str = Field(..., alias="toolName") | ||
| output_path: str = Field(..., alias="outputPath") | ||
|
|
||
|
|
||
| # Note: order matters in this union — ToolOutputRecipient is listed first so payloads | ||
| # carrying `source: "toolOutput"` match it before the literal variants get a chance. | ||
| # The literal classes don't define a `source` field, so Pydantic's overlap heuristics | ||
| # pick the right class via the presence of required fields (value/displayName vs | ||
| # source/toolName/outputPath). A `Field(discriminator="type")` cannot be used here | ||
|
Comment on lines
+690
to
+694
|
||
| # because multiple classes share the same `type` literals (literal and tool-output | ||
| # variants of the same criteria). | ||
| AgentEscalationRecipient = Annotated[ | ||
| Union[ | ||
| ToolOutputRecipient, | ||
| StandardRecipient, | ||
| AssetRecipient, | ||
| ArgumentEmailRecipient, | ||
| ArgumentGroupNameRecipient, | ||
| WorkloadRecipient, | ||
| RoundRobinRecipient, | ||
| CustomAssigneesRecipient, | ||
| ], | ||
| Field(discriminator="type"), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the Order Matters
source: Literal["toolOutput"]as a required field. These two properties are the only things that make it structurally disjoint from the literal classes below it, which share the same Pydantic Parsing BehaviorPydantic evaluates
Why a Discriminator Cannot Be UsedA Critical InvariantsThe following invariants must hold, otherwise silent mis-typing can occur:
|
||
| BeforeValidator(_normalize_recipient_type), | ||
| ] | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT - Can clarify here that even custom assignments will follow this criteria