Skip to content

feat(cli): shared host-side governance bootstrap for run and debug#1778

Open
viswa-uipath wants to merge 1 commit into
mainfrom
feat/cli-changes
Open

feat(cli): shared host-side governance bootstrap for run and debug#1778
viswa-uipath wants to merge 1 commit into
mainfrom
feat/cli-changes

Conversation

@viswa-uipath

@viswa-uipath viswa-uipath commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Extracts the governance-bootstrap helpers used by uipath run into a shared _cli/_governance_bootstrap.py module and wires the same path into uipath debug, so both commands stamp identical audit + telemetry events. Adds a CLI-side YAML → PolicyIndex compiler so the runtime layer never has to depend on pyyaml or the wire policy format.

GovernanceBootstrap dataclass

resolve_governance() returns a frozen GovernanceBootstrap (with evaluator / policy_index / enforcement_mode / dispose fields plus a wrap_runtime() method) or None when governance should not fire. Callers hand the base runtime to bootstrap.wrap_runtime(delegate, agent_name=..., runtime_id=...) and call bootstrap.dispose() from a finally block — no manual UiPathGovernedRuntime construction, no tuple unpacking.

Factory-declared identity

Wire labels for governance / App Insights come from UiPathRuntimeFactorySettings — never from the CLI:

  • agent_framework: str | None — factory declares its framework identity (e.g. "langgraph")
  • agent_type: str | None — factory declares the coarse type (e.g. "uipath_coded", "uipath_lowcode")

Both flow: factory.get_settings() → CLI → GovernanceRuntimeMetadata verbatim. Missing values default to "unknown" at the metadata boundary. is_coded_agent and low-code filename detection are removed entirely.

Eval telemetry consumes the same source: EvalSetRunCreatedEvent carries agent_type populated from the factory; EvalTelemetrySubscriber forwards it verbatim to the App Insights AgentType dimension.

Error handling

Governance is optional — a failing bootstrap must not crash the run. Every step in resolve_governance() that can raise (policy fetch, policy compile, dispatcher init, compensator / audit-manager / evaluator construction) is caught; the CLI logs a warning and runs un-governed. If a failure lands after the dispatcher spawns its background thread and registers its atexit hook, the recovery path calls dispose() to unregister and shut down so no thread leaks.

dispose() runs from CLI finally blocks, so it swallows and debug-logs both atexit.unregister and dispatcher-shutdown errors — it must never mask the primary exception. Safe to call more than once: the dispatcher's shutdown early-returns on repeat calls and atexit.unregister is a no-op for missing handlers.

cli_debug parity

cli_debug calls resolve_governance() after factory.get_settings(), computes governance_runtime_id once (ctx.conversation_id or ctx.job_id or "default"), passes the evaluator into factory.new_runtime and wraps via bootstrap.wrap_runtime — matching cli_run's shape exactly.

Runtime dependency

Depends on uipath-runtime shipping UiPathRuntimeFactorySettings.agent_framework and .agent_type. The pin will be bumped in a follow-up commit.

Adapter follow-ups (outside this PR)

Each third-party factory should declare its own wire labels in get_settings() — until they do, their governance/App Insights emits "unknown":

Repo agent_framework agent_type
uipath-agents-python (low-code) your choice "uipath_lowcode"
uipath-langchain-python "langgraph" "uipath_coded"
uipath-integrations-python/* e.g. "llamaindex", "openai_agents", "pydantic_ai", "google_adk", "agent_framework" "uipath_coded"

Test plan

  • tests/cli/test_governance_bootstrap.py — feature-flag gate, policy fetch / compile failures, dispose contract, wrap_runtime, error-path cleanup, agent_type / agent_framework forwarding
  • tests/cli/_governance/test_yaml_index.py — every supported check type + pack/rule plumbing
  • tests/cli/eval/test_eval_telemetry.py — agent_type forwarded from event to App Insights
  • Full CLI suite + functions + eval regression — passes end-to-end once the runtime pin bumps

🤖 Generated with Claude Code

Development Packages

uipath

[project]
dependencies = [
  # Exact version (copy-paste ready):
  "uipath==2.12.5.dev1017787105",

  # Any version from this PR (uncomment to use a range instead):
  # "uipath>=2.12.5.dev1017780000,<2.12.5.dev1017790000",
]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath = { index = "testpypi" }

@viswa-uipath viswa-uipath added the build:dev Create a dev build from the pr label Jul 1, 2026
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-integrations labels Jul 1, 2026
@viswa-uipath viswa-uipath force-pushed the feat/cli-changes branch 2 times, most recently from 1a11593 to 04ad14f Compare July 1, 2026 15:07
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚨 Heads up: uipath-integrations cross-tests are FAILING 🚨

Your changes may break one or more integrations in uipath-integrations-python:

  • uipath-openai-agents
  • uipath-google-adk
  • uipath-agent-framework
  • uipath-llamaindex
  • uipath-pydantic-ai

⚠️ These checks are NOT enforced by branch protection rules. Please review the failures before merging.

🔍 Inspect the failed run →

@viswa-uipath viswa-uipath force-pushed the feat/cli-changes branch 3 times, most recently from 38435f6 to 9d066a2 Compare July 1, 2026 17:59
@viswa-uipath viswa-uipath added build:dev Create a dev build from the pr and removed build:dev Create a dev build from the pr labels Jul 1, 2026
@viswa-uipath viswa-uipath added build:dev Create a dev build from the pr and removed build:dev Create a dev build from the pr labels Jul 1, 2026
@viswa-uipath viswa-uipath marked this pull request as ready for review July 1, 2026 18:48
@radu-mocanu

Copy link
Copy Markdown
Collaborator

shouldn t we apply the governance to evals as well?

Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
@radu-mocanu

Copy link
Copy Markdown
Collaborator

Overall blocking concern: this PR crosses package boundaries by making the public uipath CLI know about product/runtime identities and framework adapter discovery. uipath-python should not reference or special-case the low-code runtime, and it should not infer framework identity from framework-specific files or constants.

The clean contract is to add framework: str = "unknown" to UiPathRuntimeFactorySettings in uipath-runtime. Each registered factory should provide its own value at the existing entry-point factory seam: framework adapters set their framework (langchain, llamaindex, etc.), the functions runtime sets functions, and the low-code runtime can set low_code from its own package. The CLI should only read factory_settings.framework and forward it into GovernanceRuntimeMetadata, defaulting to unknown when settings are absent.

return None
if not response.policies:
return None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: combine under the same condition

Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_debug.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_run.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_debug.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_debug.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py Outdated
# it — the tuple is unpacked again inside the
# inner ``execute_debug_runtime``, but that
# nested scope's assignment is invisible here.
governance_dispose = governance_bootstrap[3]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This scope workaround is a sign that the bootstrap shape is wrong. Keep one governance object in the outer scope and call governance.dispose() from the final cleanup. Avoid indexing governance_bootstrap[3] and avoid unpacking the same object again inside the nested function.

Comment thread packages/uipath/src/uipath/_cli/cli_run.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_debug.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_run.py Outdated
Comment thread packages/uipath/src/uipath/_cli/cli_run.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/tests/cli/test_governance_bootstrap.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance/yaml_index.py Outdated
Comment thread packages/uipath/tests/cli/_governance/test_yaml_index.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance/yaml_index.py Outdated
Comment thread packages/uipath/tests/cli/_governance/test_yaml_index.py Outdated
Comment thread packages/uipath/tests/cli/_governance/test_yaml_index.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extracts host-side governance bootstrapping into a shared CLI module and reuses it from both uipath run and uipath debug, so they fetch/compile policy and stamp identical audit + telemetry events. It also adds a CLI-side YAML → PolicyIndex compiler and updates dependencies/tests accordingly.

Changes:

  • Added shared governance bootstrap module (_cli/_governance_bootstrap.py) and wired it into both cli_run and cli_debug.
  • Added CLI-side governance helpers (_cli/_governance/yaml_index.py) to compile YAML policies into a runtime PolicyIndex.
  • Added comprehensive tests for governance bootstrap and YAML compilation, and bumped package/dependency versions (including pyyaml).

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/uipath/uv.lock Updates lockfile for new/updated dependencies (runtime bump, PyYAML/types, etc.).
packages/uipath/pyproject.toml Bumps uipath version and updates dependencies (incl. uipath-runtime and pyyaml).
packages/uipath/src/uipath/_cli/_governance_bootstrap.py New shared governance bootstrap used by both run/debug paths.
packages/uipath/src/uipath/_cli/_governance/init.py New CLI governance helper package export.
packages/uipath/src/uipath/_cli/_governance/yaml_index.py New YAML policy compiler producing PolicyIndex for the runtime.
packages/uipath/src/uipath/_cli/cli_run.py Wires shared governance bootstrap into uipath run and manages teardown.
packages/uipath/src/uipath/_cli/cli_debug.py Wires shared governance bootstrap into uipath debug and manages teardown.
packages/uipath/src/uipath/_cli/_utils/_common.py Adds shared is_coded_agent() helper used for consistent agent-type labeling.
packages/uipath/src/uipath/_cli/_evals/_telemetry.py Reuses is_coded_agent() for consistent telemetry labeling.
packages/uipath/tests/cli/test_governance_bootstrap.py New tests covering feature-flag gating, policy fetch/compile branches, cleanup contract, and conversational flag read.
packages/uipath/tests/cli/_governance/test_yaml_index.py New tests covering YAML policy parsing and check-type compilation.
packages/uipath/tests/cli/_governance/init.py Initializes governance test package.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/uipath/src/uipath/_cli/cli_run.py
Comment thread packages/uipath/src/uipath/_cli/cli_run.py Outdated
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py
Comment thread packages/uipath/src/uipath/_cli/_governance_bootstrap.py
@viswa-uipath viswa-uipath force-pushed the feat/cli-changes branch 2 times, most recently from 17fff0a to cd0d458 Compare July 2, 2026 12:10
@viswa-uipath viswa-uipath requested a review from radu-mocanu July 2, 2026 12:14
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🚨 Heads up: uipath-langchain cross-tests are FAILING 🚨

Your changes may break the uipath-langchain-python integration.

⚠️ These checks are NOT enforced by branch protection rules. Please review the failures before merging.

🔍 Inspect the failed run →

if entrypoint == "agent.json":
return "LowCode"
return "Coded"
self._current_agent_type: str | None = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is a breaking change - you need to keep the fallback on the previous implementation. Also make sure you the new values match these

await factory.dispose()
if live_tracking_processor is not None:
live_tracking_processor.shutdown()
if governance_bootstrap is not None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the runtime should be disposed before the factory

await factory.dispose()
if live_tracking_processor is not None:
live_tracking_processor.shutdown()
if governance_bootstrap is not None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

# with ``agent_type`` / ``agent_framework`` on the settings model.
return UiPathRuntimeFactorySettings( # type: ignore[call-arg]
agent_type=_AGENT_TYPE_CODED,
agent_framework=_AGENT_FRAMEWORK,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the functions runtime is not langchain - it's pure python

Extracts the governance-bootstrap helpers used by ``uipath run`` into a
shared ``_cli/_governance_bootstrap.py`` module and wires the same path
into ``uipath debug`` so both commands stamp identical audit + telemetry
events. Adds a CLI-side YAML -> ``PolicyIndex`` compiler so the runtime
never has to depend on ``pyyaml`` or the wire policy format.

GovernanceBootstrap dataclass
-----------------------------

``resolve_governance()`` returns a ``GovernanceBootstrap`` (frozen
dataclass with ``evaluator`` / ``policy_index`` / ``enforcement_mode``
/ ``dispose`` fields plus a ``wrap_runtime()`` method) or ``None`` when
governance should not fire. Callers hand the base runtime to
``bootstrap.wrap_runtime(delegate, agent_name=..., runtime_id=...)``
and call ``bootstrap.dispose()`` in ``finally`` -- no manual
``UiPathGovernedRuntime`` construction and no tuple unpacking.

Factory-declared identity
-------------------------

Framework and agent-type wire labels are advertised by the factory via
``UiPathRuntimeFactorySettings.agent_framework`` and
``UiPathRuntimeFactorySettings.agent_type``. The CLI reads both from
``factory.get_settings()`` and forwards them verbatim to
``GovernanceRuntimeMetadata``; the OSS package no longer classifies
projects by filename or hardcoded marker registries. Missing values
default to ``"unknown"`` at the metadata boundary.

Eval telemetry consumes the same source: ``EvalSetRunCreatedEvent``
carries ``agent_type`` populated from the factory, and
``EvalTelemetrySubscriber`` forwards it verbatim to the App Insights
``AgentType`` dimension. The ``is_coded_agent`` helper and low-code
filename detection are removed entirely.

Error handling
--------------

Governance is optional -- a failing bootstrap must not crash the run.
Every step in ``resolve_governance()`` that can raise (policy fetch,
policy compile, dispatcher init, compensator / audit-manager /
evaluator construction) is caught; the CLI logs a warning and runs
un-governed. If a failure lands after the dispatcher spawns its
background thread and registers its ``atexit`` hook, the recovery
path calls ``dispose()`` to unregister and shut down so no thread
leaks.

``dispose()`` runs from CLI ``finally`` blocks, so it swallows and
debug-logs both ``atexit.unregister`` and dispatcher-shutdown errors
-- it must never mask the primary exception. Safe to call more than
once: the dispatcher's ``shutdown`` early-returns on repeat calls and
``atexit.unregister`` is a no-op for missing handlers.

cli_debug parity
----------------

``cli_debug`` calls ``resolve_governance()`` after
``factory.get_settings()``, computes ``governance_runtime_id`` once
(``ctx.conversation_id or ctx.job_id or "default"``), passes the
evaluator into ``factory.new_runtime`` and wraps via
``bootstrap.wrap_runtime`` -- matching ``cli_run``'s shape exactly.

Depends on the runtime bumping ``UiPathRuntimeFactorySettings`` with
the two new fields; the pin will be updated in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build:dev Create a dev build from the pr test:uipath-integrations test:uipath-langchain Triggers tests in the uipath-langchain-python repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants