feat(skills): add skills support to advanced agents with sandboxed cli tool - #989
feat(skills): add skills support to advanced agents with sandboxed cli tool#989mariadhakalUipath wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds initial “skills” plumbing to advanced agents and introduces a sandbox-oriented internal tool for running uip commands inside an agent workspace.
Changes:
- Introduces a new
create_uipath_cli_tool()(uipath_cli) StructuredTool that runs oneuipcommand per invocation with command validation. - Adds unit tests covering command parsing, validation, subprocess result mapping, and timeout behavior for the new tool.
- Extends advanced-agent builders to accept and forward a
skillsparameter into the underlying deepagents agent construction.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
tests/agent/tools/internal_tools/test_uipath_cli_tool.py |
Adds test coverage for the new sandboxed CLI tool. |
src/uipath_langchain/agent/tools/internal_tools/uipath_cli_tool.py |
Implements the sandboxed uipath_cli tool and its input/output schemas. |
src/uipath_langchain/agent/tools/internal_tools/__init__.py |
Exports create_uipath_cli_tool from the internal tools package. |
src/uipath_langchain/agent/advanced/agent.py |
Adds skills parameter propagation to deepagents agent creation/wrappers. |
d79b0c1 to
5b5dea8
Compare
5b5dea8 to
36f2b2c
Compare
36f2b2c to
2f7b1c1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/uipath_langchain/agent/advanced/agent.py:45
- The
create_advanced_agentdocstring documentsmemorybut not the newly addedskillsparameter, which is part of the public API. Update the docstring to describe whatskillsdoes and how an empty value is handled.
"""Create a deepagents agent with planning, filesystem, and sub-agent tools.
``memory`` is a list of file paths loaded via deepagents' ``MemoryMiddleware``:
each is read from ``backend`` and injected into the system prompt every turn,
and the model maintains them with ``edit_file``. Empty disables the middleware.
"""
2f7b1c1 to
4922fb1
Compare
4922fb1 to
bff67ea
Compare
bff67ea to
df1cb80
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/uipath_langchain/agent/advanced/agent.py:42
create_advanced_agentnow acceptsskills, but the docstring only documentsmemory. Since empty skills collapses toNone(disabling skills support), documenting this behavior would help callers understand how to enable/disable the feature.
skills: Sequence[str] = (),
) -> CompiledStateGraph[Any, Any, Any, Any]:
"""Create a deepagents agent with planning, filesystem, and sub-agent tools.
``memory`` is a list of file paths loaded via deepagents' ``MemoryMiddleware``:
9bbae6c to
850386e
Compare
8fd9bf3 to
923cb66
Compare
7d9ca46 to
b02dd2b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/uipath_langchain/agent/tools/internal_tools/uipath_cli_tool.py:37
- The tool description says “A negative exit_code means the command was refused”. That’s inaccurate because real subprocess exit codes can be negative on Unix when terminated by a signal. The contract in UiPathCliOutput/test cases is specifically “exit_code == -1000 indicates refusal”, so the description should match to avoid confusing the agent.
"Run a single UiPath `uip` command in the agent workspace; returns "
"exit_code/stdout/stderr. Only the uip/uipath binaries, one command per call; "
"shell chaining is refused. A negative exit_code means the command was refused "
"before it ran and stderr explains why. Use `subdir` to target a scaffolded "
"project folder."
src/uipath_langchain/agent/tools/internal_tools/uipath_cli_tool.py:167
- On timeout, proc.kill() can raise ProcessLookupError if the process exits between the timeout firing and the kill call. That would crash the tool instead of returning the recoverable _REJECTED_EXIT_CODE payload.
except asyncio.TimeoutError:
proc.kill()
await proc.wait()
return _rejected(
echoed, f"Command timed out after {_COMMAND_TIMEOUT_SECONDS}s."
src/uipath_langchain/agent/advanced/agent.py:110
- This PR introduces a sandboxed uipath_cli tool, but create_advanced_agent only forwards the
skillslist into deepagents and never registers the new tool. The new tool module’s docstring says it is “injected programmatically by the agent graph builder when UiPath skills are active”, but there is currently no call site for create_uipath_cli_tool in src/ (verified by repo search). If skills are expected to prescribeuipcommands, the agent needs the tool added to its tool list when skills are enabled (and a FilesystemBackend workspace exists).
memory=list(memory) or None,
middleware=list(middleware),
skills=list(skills) if skills else None,
b02dd2b to
4083836
Compare
| "A single `uip` command without the binary prefix, e.g. `pack`, " | ||
| "`solution publish`. One command only; no shell operators." | ||
| ), | ||
| examples=["pack", "solution publish", "codeagent init"], |
There was a problem hiding this comment.
codeagent init is not a valid command; the cli returns unknown command 'codeagent'. use codedagent init here, and add a schema assertion so the example stays aligned with the actual cli.
There was a problem hiding this comment.
Thank you for correcting me. I have changed the example command and also added the schema assertion for the example commands.
| response_format: ResponseFormat[Any] | None = None, | ||
| memory: Sequence[str] = (), | ||
| middleware: Sequence[AgentMiddleware[Any, Any]] = (), | ||
| skills: Sequence[str] | None = None, |
There was a problem hiding this comment.
this is a logic-changing public change, but the pr does not bump the package version. main is already at 0.16.2 while this branch still has 0.16.1. rebase and bump to the next version in pyproject.toml before merge.
There was a problem hiding this comment.
Okay will do this before I merge.
| echoed, f"Invalid subdir '{subdir}': escapes the workspace." | ||
| ) | ||
|
|
||
| return await _run_uip_subprocess(binary, args, run_dir, echoed) |
There was a problem hiding this comment.
this still executes the cli arguments directly, without the cli owning and enforcing a safe mode. the parser currently accepts commands such as solution delete and admin tenants delete, so resolving the previous thread did not address the version-skew problem. let s add the safety flag in the cli and always append it here before shipping this tool.
There was a problem hiding this comment.
This is really good point we were thinking about this, the reason I don't have it implemented is:
- We are using the user's token to run these commands so it is already tenant and folder scoped, agent can only run the commands that user is allowed.
- This tool is used by both conversational and autonomous low-code agent, conversational agent uses the interactive mode to ask user to run the command and only the autonomous low-code agent is running the command by itself so I added prompt-based guardrail to end the execution if there is any destructive command.
- It is only attached to agent if the skills are enabled so it will only follow the instructions there and if the user is using this tool in coded agent then it becomes their responsibility to filter the commands.
Please guide me if I am missing anything that I should have considered for this, and I can work through it.
| proc.communicate(), timeout=_COMMAND_TIMEOUT_SECONDS | ||
| ) | ||
| except asyncio.TimeoutError: | ||
| proc.kill() |
There was a problem hiding this comment.
proc.kill() can raise ProcessLookupError when the child exits in the timeout and kill race. i reproduced that path and the tool crashes instead of returning the recoverable timeout payload. catch that race and still await proc.wait().
There was a problem hiding this comment.
Fix:
except asyncio.TimeoutError:
try:
proc.kill()
except ProcessLookupError:
pass
await proc.wait()
wait_for timing out only means the child hadn't finished when the timer fired it can
exit before kill() runs, and the transport teardown that nulls _proc makes kill()
raise. await proc.wait() stays outside the try so it runs on both paths (safe after
teardown: _wait() returns the cached returncode immediately). Catching only
ProcessLookupError so a real PermissionError still surfaces.
Added Regression test: test_run_times_out_when_child_exits_during_kill to verify it fails
with an uncaught ProcessLookupError against the unpatched code.
| response_format=response_format, | ||
| memory=list(memory) or None, | ||
| middleware=list(middleware), | ||
| skills=list(skills) if skills else None, |
There was a problem hiding this comment.
this tool is still not wired into the advanced agent. with skills enabled, we only forward the skill paths and leave tools unchanged, so the agent has no way to call uipath_cli. let s add it here for the filesystem-backed skills path and cover the final tool list in a test.
There was a problem hiding this comment.
We have this tool behind feature flag and we wired it when creating advanced agent graph for low-code agents if skills are enabled instead of wiring it to advanced agent. The scope for now is to attach the tool for low-code agents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4083836 to
b8d13be
Compare
|



Title: Uipath Skills with Cli tool for advanced agents.
Summary
Adds skills feature for advanced agents. Agent can act on it — run uip commands, review what it ran, and verify what it scaffolded.