Repository navigation
Conversation
ionmincu
force-pushed
the
feat/prodev-1742-conversational-a2a
branch
from
October 9, 2026 12:53
f684ff6 to
caf3bca
Compare
- Call an existing UiPath conversational agent as an A2A tool: resolve the
folder key and the conversational release, then talk to AgentHub
a2a/{folderKey}/{releaseId} through the existing A2A client.
- Accept the conversationalAgent tool resource next to the a2a resource; a
missing processName errors at call time instead of rejecting the agent, and
requireConversationalConfirmation is applied per tool.
- Address conversations by conversation_id / new_conversation with a capped
per-tool registry, per-conversation locks and terminal-state handling;
advanced mode honors the same arguments without a tool wrapper.
- tool_factory leaves the new resource to the A2A path (no job tool).
- Bump uipath to >=2.14.37 and the package to 0.18.33.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ionmincu
force-pushed
the
feat/prodev-1742-conversational-a2a
branch
from
October 9, 2026 15:40
caf3bca to
6e9c29a
Compare
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


PR stack (PRODEV-1742), merge in this order
conversationalAgenttool type, plus itsprocessbinding in the packager. Wait for the@uipath/agents-storage-schemas1.44.0 and@uipath/tool-agent2.2.0 (packager) releases.AgentToolType.CONVERSATIONAL_AGENTandAgentConversationalAgentToolResourceConfig. Wait for the uipath 2.14.37 PyPI release.a2a/{folderKey}/{releaseId}over A2A. Runuv lockfirst, then wait for the uipath-langchain 0.18.33 release. ← this PRconversationalAgenttools (never a job tool), bump dependencies. Runuv lockfirst, then deploy the runtime.conversationalAgenttool resource. Can merge any time after [1/5] is released, with the flag off. First bump@uipath/agents-storage-schemasto^1.44.0and@uipath/tool-agentto^2.2.0(older packagers emit noprocessbinding for this tool), and set the flag default back tofalse.Turn on the Flow flag
canvas.nodes.agent-tool-conversational-a2aonly after [4/5] is deployed.Summary
Lets the agents runtime call an existing UiPath conversational agent as an A2A tool (Jira PRODEV-1742).
Why a tool type, not an
a2avariantAn
a2aresource without aslugmakes older runtimes reject the whole agent (AgentA2aResourceConfigrequiresslug, and_normalize_resourceskeepsa2a). An unknown tooltypegoes throughTOOL_MAP.get(t.lower(), "Unknown"), so older runtimes skip just that tool. A tool type also gets the solutionprocess.<processName>binding from the packager, so@resource_override(process, process_name)takes effect. Precedent:AgentToolType.FLOW/FUNCTION.create_a2a_tools_and_clients,open_a2a_tools,A2aClient,_create_a2a_tool) acceptAgentConversationalAgentToolResourceConfig(from uipath-python#1931) next toAgentA2aResourceConfig, readingproperties.process_name,folder_pathandcached_agent_card. Client, send and trace span code is reused; URL resolution, labels and the conversation interface differ (see Conversation handles).processNameloads, then errors at call time ("conversational agent tool '' has no processName") as an error ToolMessage.requireConversationalConfirmationis applied per tool insidecreate_a2a_tools_and_clients(..., is_conversational=...)from the tool's own config, gated on the agent being conversational, liketool_factorydoes for other tools.tool_factory._build_tool_for_resourcereturnsNonefor the new resource (test added), so it never reaches the process/job path.A2aClient.get()): the folder key comes fromsdk.folders.retrieve_folder_key_async, trying the configuredfolder_pathfirst and falling back to the execution folder (UIPATH_FOLDER_PATH) only when it is missing or raisesFolderNotFoundException; the fallback logs a warning naming both folders. The release is read withGET /orchestrator_/odata/Releases?$filter=Name eq '...'&$select=Id,Name,ProcessType,IsConversational(quotes doubled) viasdk.api_client, and filtered client-side toProcessType == 'Agent'andIsConversational is True, since I could not confirm server-side filtering. The SDK has no releases service, so this is a small private helper decorated with@resource_override(resource_type="process", resource_identifier="process_name"); as a tool resource it gets the solutionprocess.<processName>binding from the packager. The URL goes throughresolve_service_url("agenthub_/a2a/{folderKey}/{releaseId}"), soUIPATH_SERVICE_URL_AGENTHUBis honored; otherwise it is{sdk base url}/agenthub_/a2a/{folderKey}/{releaseId}.ValueErrors so the tool node turns them into error ToolMessages (anAgentRuntimeErrorwould terminate the agent); a test drives this through the real tool-node error handling. Cases: no folder configured or executing; folder(s) not found; "'' in folder '' is not a published conversational agent". Graph construction never touches the network.sanitize_tool_name(resource.name), description from the cached card or the resource description; metadata carriesprocess_nameinstead ofslug. Protocol version comes from the cached card, defaulting to1.0(AgentHub serves 0.3 and 1.0).uipathto>=2.14.37, <2.15.0and the package to0.18.33.Conversation handles (conversational agent tool only)
Remote A2A keeps
A2aToolInputand its{task_id, context_id}state unchanged.Why: the tool used to hold one hidden conversation, so the model could not start a fresh one or run two in parallel, and in advanced (deepagents) mode, where tools run without a
UiPathToolNodewrapper, every call opened a new child conversation. It also removes the terminal-task dead end: AgentHub's conversational endpoint hascontextId = conversationId = taskIdand rejects messages to a terminal task, which used to fail every later call.Input (
A2aConversationInput):message(required),conversation_id(continue that conversation),new_conversation(start a separate one;nullmeans false). Both together returnsinvalid_arguments. The tool description says what omitting both does: the most recent open conversation may continue or a new one starts, so passconversation_idto continue a specific one.Result (ToolMessage content, JSON):
{"agent_response", "conversation_id", "task_state"}, plus"note"("The previous conversation was not found; a new conversation was started.") when AgentHub answers with a different id than the one sent.{"error": "conversation_closed" | "invalid_arguments" | "request_failed", "conversation_id": <id or null>, "message": <hint>}; the closed hint says to usenew_conversation=true. Failures to resolve the process, folder or release (including a missingprocessName) arerequest_failedwith the clear message, so they never abort an advanced-mode run.InvalidRequestError; any other error staysrequest_failedand keeps the state.State (ReAct wrapper):
tools_storage[tool.name] = {"current": cid | None, "conversations": {cid: {"task_id", "state": "open" | "closed", "last_used": <counter>}}}. The whole entry is replaced per call (not merged by conversation), which is what lets eviction and closing propagate;merge_dictsreplaces per top-level key, so this is safe. ReAct dispatches one tool call per node visit, so each call sees the previously committed entry and nothing is lost. Capped at 20, evicting the least recently used, closed ones first (and clearingcurrentif it is evicted). The earlier{task_id, context_id}entry is migrated into a one-conversation registry.Resolution:
new_conversationsends no IDs and becomescurrent. An explicitconversation_idis sent as the A2Acontext_id(with itstask_idwhen known); a known closed one returnsconversation_closedwithout a call, an unknown one is still sent (handles come from replayed history), and it becomescurrentonly when there is none. With neither,currentis used, or a new conversation starts. A terminal task closes the conversation and clearscurrent.Advanced mode: tools there run without a wrapper and
tools_storageis not used. Tool instances outlive a run (the runtime factory caches compiled graphs), so nothing is stored: a call with neither argument always starts a new conversation, and the model continues one by passing theconversation_idfrom a previous result (CAS replays tool results as ToolMessages, so the id survives across turns).Concurrency: sends are serialized per conversation with an
asyncio.Lockper conversation id (held per tool, so effectively(tool, cid)). The locks live in aWeakValueDictionary, so ids the model invents do not accumulate. Different conversations run in parallel.The A2A span carries
conversation_idwhen one is known.Not done
uv.lockis not updated:uipath 2.14.37is not on PyPI yet. Refresh it (uv lock) once #1931 is released. Tests were run locally against the uipath-python branch installed editable (no override committed).Test plan
uv run pytest tests/agent/tools/test_a2a_tool.py tests/agent/tools/test_a2a_sdk_v1_migration.py tests/agent/tools/test_tool_factory.py tests/test_no_circular_imports.py: 306 passedruff check .,ruff format --check .,python scripts/lint_httpx_client.py,mypy srcplus the two touched test files: clean🤖 Generated with Claude Code