feat(guardrails): let tool-scope judge guardrails read the files a tool call mentions - #1103
Merged
Merged
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Current-call selection and nested payload scanning have unresolved issues that can produce incorrect guardrail decisions.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds call-scoped attachment discovery for Tool-scope guardrails, including pre- and post-execution validation.
Changes:
- Resolves referenced attachments from tool arguments and results.
- Adds bounded scanning, deduplication, and metadata fallback.
- Adds guardrail and end-to-end tests.
- Bumps version to 0.18.12.
| File | Description |
|---|---|
uv.lock |
Synchronizes the locked package version. |
tests/agent/guardrails/test_tool_guardrails_subgraph_run.py |
Adds end-to-end tool guardrail coverage. |
tests/agent/guardrails/test_guardrail_nodes.py |
Tests guardrail attachment forwarding. |
tests/agent/guardrails/test_attachment_refs.py |
Tests attachment discovery behavior. |
src/uipath_langchain/agent/guardrails/guardrail_nodes.py |
Integrates scope- and stage-aware attachment resolution. |
src/uipath_langchain/agent/guardrails/attachment_refs.py |
Implements bounded attachment reference collection. |
pyproject.toml |
Bumps the package version. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
apetraru-uipath
force-pushed
the
feat/guardrail-judge-tool-scope
branch
2 times, most recently
from
September 20, 2026 19:57
e5e77fd to
a910bcd
Compare
…ol call mentions Tool-scope guardrails no longer skip attachments. Before the tool runs, the judge receives references for the attachments named in the call's arguments; after it runs, for those named in the result. Only ids the run's registry holds are forwarded, with the registry's name and type, so a call that mentions no file forwards none, and a mention of a file the run never held is skipped rather than sent to the backend for lookup. Agent and LLM scope keep reading the run's registry; both paths build references through one collector. AL-593 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
0.18.12 shipped from main on 2026-09-21 without this change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
apetraru-uipath
force-pushed
the
feat/guardrail-judge-tool-scope
branch
from
September 21, 2026 21:26
a910bcd to
0c6aeb1
Compare
… that name Review finding on #1103: with two calls to the same tool in one AI message, the tool-scope guardrail read "the first call named tool_name", so the second pre-execution evaluation judged (and resolved attachments for) the first call, and once the first ToolMessage was appended it read nothing at all. The node now selects the call the way the tool node does (extract_current_tool_call_index) before execution, and the call the last ToolMessage answers after execution; payload, deterministic input extraction and attachment resolution all use that selection. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
andreizdrali-uipath
approved these changes
Sep 22, 2026
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.




What
Tool-scope LLM-as-Judge guardrails now read the files a tool call touches. Until now
guardrail_nodes.pyexcluded Tool scope from attachment forwarding (_ATTACHMENT_SCOPES = {Agent, Llm}), because feeding a tool-scope judge from the whole-run registry would ship every file on every tool call. This PR replaces that exclusion with call-scoped discovery:{"ID": "<uuid>"}objects, the shape the model emits andreplace_job_attachment_idsexpands later inside the tool wrapper). Only ids the run's registry holds are forwarded, with the registry's name and MIME type: the registry is the set of files this run legitimately has (agent input plus files its tools returned), so a mention of a file the run never held is skipped rather than sent to the backend for an Orchestrator lookup.ToolMessageresult, so a tool that returns a file (output-file, batch-transform, process tools) gets it judged.IDthat is not an attachment (queue item, job) is never forwarded._collect), so the backend sees identical references whichever scope produced them.This is the generic mechanism AL-596 (guardrails on the IXP extraction tool) builds on: the IXP tool takes a single
JobAttachment, and itsinterrupt()fires inside the tool node, after the pre-execution judge has run.Code
agent/guardrails/attachment_refs.py:resolve_referenced_attachments(data, job_attachments, guardrail)walks the payload (depth- and size-bounded), resolves mentions, dedupes and caps at 5;resolve_guardrail_attachmentskeeps its signature. Never raises.agent/guardrails/guardrail_nodes.py:_resolve_attachmentspicks the source by scope and stage, reusing the tool node's existing argument/result extractors. Plain-text tool results are skipped before parsing (a mention always carries the literalIDkey), so the output extractor's warning is not triggered for every text result.Not touched: the subgraph wiring (tool nodes were already wrapped pre and post),
_VALIDATOR_ALLOWED_STAGES, the text-only retry on an attachment-caused 400, the escalate action payload, the coded-agent middleware stack.Backend and designer
No helix or Studio Web change is needed:
llm_as_judgealready allows Tool scope at both stages, the validate request has no scope or stage field, the designer already offers Tool scope for the judge (no stage picker, so a Tool-scope guardrail runs at both stages), and theappliesTodropdown renders from the definition contract. File contents are used only when helix hasEnableGuardrailAttachmentson (UiPath/Agents#6256).Tests
Review follow-up (093f3e4): the tool-scope node now judges the current tool call. With two calls to the same tool in one AI message it used to read the first call named
tool_name(and, once the first ToolMessage was appended, nothing at all). Pre-execution selects the call viaextract_current_tool_call_indexexactly like the tool node; post-execution selects the call the last ToolMessage answers. Payload, deterministic input extraction and attachment resolution share the selection. Two node tests cover the two-calls case.tests/agent/guardrails/test_attachment_refs.py: 18 new cases for the referenced path (named file, no file, nested, id the run never held even with inline name/type, registry wins over inline fields, non-attachment UUID, non-UUID id, dedupe, cap, prompts-only, model instances, bounded scan, odd payloads, parity with the registry path).tests/agent/guardrails/test_guardrail_nodes.py: tool pre/post node tests replacetest_tool_scope_node_never_resolves_attachments; Agent/LLM pre-execution withappliesTo = Filesregression.tests/agent/guardrails/test_tool_guardrails_subgraph_run.py: a compiled tool subgraph around a realUiPathToolNode: a failing judge blocks before the tool implementation runs and saw exactly the referenced file; a passing judge lets the tool run and judges its result.tests/agent/guardrails: 351 passed. ruff, ruff format, mypy clean on the changed files; every new line in the two modules covered.CI note: the
Integration testing / * cloudjobs fail withUiPathPermissionDeniedError: Forbidden (403)from the cloud test environment on this and on other PRs today (fix/code-interpreter-warmup); thealphavariants pass.Verified locally
Same runtime checkout, local helix on UiPath/Agents#6256 with
EnableGuardrailAttachmentson, real Orchestrator attachments (alpha, APetraruTenant). Agent: the Joke Agent with the built-in Analyze Files tool, one LLM-as-Judge guardrail at Tool scope on that tool (Block), the user prompt referencing{{input.file}}, no agent-scope judge.cats+joke_brief_unsafe.txtAnalyze_Fileswith the attachment →tool_pre_execution_judge_file_before_analyze_files→ helix validate with exactly oneGET …/odata/Attachments(<id>)→Flagged: "joke_brief_unsafe.txt" contains …→ blocked (AGENT_RUNTIME.TERMINATION_GUARDRAIL_VIOLATION, reason "Unsafe file content"). The tool never ran.gardening+joke_brief_clean.txtAllowed(one attachment lookup) → Analyze Files runs → tool post-execution judgeAllowedwith no Orchestrator lookup (plain-text result names no file) → agent post PII passes →✓ Successful executionThe agent-scope PII guardrail on the same runs receives the run's attachments and ignores them (helix gate), as before.
Studio Web Debug on the local robot, IXP Test Agent Solution (tool-scope judge added on the
Json file IXPtool from the designer):Version bumped to 0.18.13 (0.18.12 was released from main on 2026-09-21 without this change; branch rebased onto it).
Jira: AL-593 (consumer: AL-596).
🤖 Generated with Claude Code