feat(platform): add GovernanceService for agenticgovernance_ ingress#1738
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new GovernanceService to uipath-platform to wrap the org-scoped agenticgovernance_ ingress (policy retrieval + compensating govern call), and exposes a new resolve_trace_id() helper so callers can capture the effective trace id before moving work off-thread.
Changes:
- Introduces
uipath.platform.governance.GovernanceServicewith sync/asyncretrieve_policy()andcompensate()APIs, plus Pydantic request/response models. - Adds public
resolve_trace_id(fallback)helper touipath.platform.commonfor retrieving the current trace id as a normalized 32-char hex string. - Adds a comprehensive test suite for governance + trace-id resolution and bumps
uipath-platformversion0.1.71→0.1.72.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath-platform/tests/services/test_governance_service.py | Adds coverage for governance policy retrieval, compensation payload behavior, and resolve_trace_id() normalization/fallbacks. |
| packages/uipath-platform/src/uipath/platform/governance/policy.py | Defines PolicyResponse response model with safe defaults and extra-field tolerance. |
| packages/uipath-platform/src/uipath/platform/governance/compensate.py | Defines FiredRule and GovernRequest models with wire-format aliases for compensation POST payloads. |
| packages/uipath-platform/src/uipath/platform/governance/_governance_service.py | Implements the GovernanceService API, URL/header composition, agentType param mapping, and job-context auto-fill. |
| packages/uipath-platform/src/uipath/platform/governance/init.py | Exposes governance service/models as a public module. |
| packages/uipath-platform/src/uipath/platform/common/_base_service.py | Adds resolve_trace_id() helper alongside existing trace header injection logic. |
| packages/uipath-platform/src/uipath/platform/common/init.py | Re-exports resolve_trace_id from uipath.platform.common. |
| packages/uipath-platform/src/uipath/platform/_uipath.py | Adds UiPath.governance cached_property for convenient access from the main client. |
| packages/uipath-platform/pyproject.toml | Bumps package version to 0.1.72. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nsate - Regenerate uv.lock in uipath-platform and uipath after the 0.1.71 -> 0.1.72 bump so `uv sync --locked` (CI lint check) passes. - In `_build_govern_payload`, use `wire_key in payload` instead of truthiness — `exclude_none=True` already drops caller-None fields, so presence is the right "was it supplied?" signal. The previous truthiness check would silently replace a caller-supplied empty string with the env-backed UiPathConfig value, violating the documented precedence (caller > UiPathConfig > omit). Caught by Copilot review on PR #1738. - Add regression test pinning the empty-string contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
27d012b to
cfc92c0
Compare
cfc92c0 to
cf5c49e
Compare
🚨 Heads up:
|
cf5c49e to
c49cfc6
Compare
41bd919 to
08e0d09
Compare
08e0d09 to
6b9a74a
Compare
6b9a74a to
eb9ec46
Compare
eb9ec46 to
acec4dd
Compare
…governance_ ingress Declare GovernancePolicyProvider / GovernanceCompensationProvider in uipath-core alongside EvaluatorProtocol — runtime consumers depend on the lowest layer; concrete providers live outside this package. Add GovernanceService in uipath-platform for the agenticgovernance_ ingress: - retrieve_policy(*, is_conversational=None) — GET the tenant policy pack. - compensate(*, hook, validators, rules, data, trace_id, ...) — POST the compensating /runtime/govern call when a guardrail_fallback rule matches; job-context fields auto-fill from UiPathConfig with caller values (including empty strings) taking precedence over the env-backed fallback. - Honors UIPATH_SERVICE_URL_AGENTICGOVERNANCE for local dev — the override path injects routing headers since the platform router is bypassed. Add UiPathPlatformGovernanceProvider as the dedicated adapter that satisfies both core protocols by delegating to the service; runtime consumers receive this object via constructor injection without importing uipath-platform directly. Add resolve_trace_id() helper in platform common to capture the canonical trace id before hopping off the OTel-context-owning thread. The runtime-side wrapper that consumes these protocols is intentionally not in this PR — it will land in uipath-runtime in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
acec4dd to
39bfaba
Compare
|
🚨 Heads up:
|



Summary
Platform-side foundation for UiPath agent governance — protocols in
uipath-coreand a concreteGovernanceService+ provider adapter inuipath-platform. The runtime-side wrapper that consumes these will land inuipath-runtimein a follow-up PR.uipath-core— declaresGovernancePolicyProvider/GovernanceCompensationProviderprotocols (runtime_checkable) alongsideEvaluatorProtocol. Owns the wire-format modelsPolicyContext,PolicyResponse,FiredRule,GovernRequest. No registry or concrete provider —uipath-coredoes not depend onuipath-platformoruipath-runtime.uipath-platform— addsGovernanceService(sdk.governance) for the org-scopedagenticgovernance_ingress with public ergonomic methods:retrieve_policy(*, is_conversational=None)+ async — GET the tenant policy pack.compensate(*, hook, validators, rules, data, trace_id, src_timestamp, agent_name, runtime_id, folder_key=None, job_key=None, process_key=None, reference_id=None, agent_version=None)+ async — POST the compensating/runtime/governcall; buildsGovernRequestinternally; job-context fields auto-fill fromUiPathConfigwith caller values (including empty strings) taking precedence over the env-backed fallback.UIPATH_SERVICE_URL_AGENTICGOVERNANCEfor local dev — the override path injects routing headers since the platform router is bypassed.uipath-platform— addsUiPathPlatformGovernanceProvideras the dedicated adapter that satisfies both core protocols by delegating to the service. Runtime consumers receive this object via constructor injection without importinguipath-platformdirectly. Construct viaUiPathPlatformGovernanceProvider(service=UiPath().governance)or by passingconfig/execution_context.uipath-platform— adds publicresolve_trace_id(fallback)helper incommon/_base_service.pyso callers can capture the trace id on the hook thread before hopping to a background pool.Bumps:
uipath-core0.5.20 → 0.5.21,uipath-platform0.1.71 → 0.1.73,uipath2.11.6 → 2.11.9.Motivation
Replaces hand-rolled
urllibHTTP inuipath-runtime-pythonPRs #121 and #123 per radu-mocanu's review comment: "tenant/org/access token etc. are platform concerns. The runtime's concern is just managing the policies."The runtime can't import
uipath-platform(dependency direction is the other way), so the protocols live inuipath-core. The runtime layer will accept the provider via constructor injection — no global registry, no hidden imports across the layer boundary.After this lands (and the follow-up runtime PR), the runtime gives up
urllib, the browser-UA WAF workaround, manualUIPATH_ACCESS_TOKENreads, manual org/tenant resolution, and the import-timeuipath-platformdependency —BaseServiceprovides all of that, and the runtime depends only onuipath-coreprotocols + a constructor-injected provider.Design choices
uipath-core, concrete provider inuipath-platform— keepsuipath-coreas the lowest layer with no upward dependencies; runtime consumers depend only on the protocols. No module-level provider registry — providers move via constructor injection.runtime_checkableProtocols — any object exposingget_policy(context)/compensate(request)satisfies the contract.compensate(*, hook, ...)is the ergonomic SDK shape that matchesretrieve_policy's pattern and stays consistent with the rest of the platform services. The internal_compensate(request)is the worker the provider adapter calls to satisfy the protocol shape without unpacking.UiPathPlatformGovernanceProvideris the dedicated adapter — implements both core protocols by delegating toGovernanceService. The service is the SDK surface; the provider is the runtime-facing contract.RequestSpec+Endpoint): theagenticgovernance_ingress uses{origin}/{org_id_uuid}/agenticgovernance_/...— the org UUID, not the slugUiPathUrl.scope_urlproduces. Documented in_build_org_scoped_request.UIPATH_SERVICE_URL_AGENTICGOVERNANCEfor local dev —_build_org_scoped_requestresolves the override itself (mirroring whatBaseService.requestdoes for paths that fitscope_url) and injects routing headers, because the org-UUID URL shape can't go through the standardscope_urlflow.is_conversational: bool | Nonerather than an enum: matches the runtime's existing_agent_is_conversationalflag. TheTrue/False/None → conversational/autonomous/omitwire mapping is an implementation detail of the service.resolve_trace_idreturns the value, doesn't mutate headers: the existing_inject_trace_contextonly injects the header. Compensation needs the id in the request body, and must resolve on the hook thread before the pool hop because OpenTelemetry context is thread-local.@cached_propertyonsdk.governance: matchesbuckets/attachments/folders. Avoids freshhttpx.Client/AsyncClientper access in the background compensation loop.PolicyResponse.mode: EnforcementMode | Nonewith lenient coercion: a@field_validator(mode="before")maps unknown wire values toNoneinstead of raising, so a server-side mode addition can't break agent startup.Out of scope (follow-up PRs)
GovernanceRuntime(callsprovider.get_policy()at execute/stream start, exposescompensate()to agent code) will land inuipath-runtime, not here. CLI wiring (cli_run/cli_debugconstructing the runtime with the injected provider) lives there too.EnablePythonGovernanceCheckerfeature flag: kept in place until test-pypi rollout finishes; will be removed in a separate PR.Test plan
ruff check ./ruff format --check .— clean across all three packagesmypy src tests— cleanpytest --no-cov— full suite greentests/governance/test_providers.py): protocol conformance (runtime_checkableaccept/reject), wire-format models (defaults, camelCase aliasing + intentional snake_casesrc_timestamp, optional job-context excluded whenNone, lenientEnforcementModecoercion).tests/services/test_governance_service.py(28) andtests/services/test_governance_provider.py(9): policy fetch happy path / defaults / header + bearer wiring /agentTypequery param (parameterized True/False) / missing org-tenant / HTTP errors / async; compensate payload aliasing / job-context auto-fill from env / caller override precedence / caller empty-string preserved over env / omitted keys / HTTP errors / async;GovernanceService.get_policysatisfies the policy protocol;UiPathPlatformGovernanceProviderconstruction (existing service, built-from-config, requires service or full kwargs) + both-protocol conformance + delegation;UIPATH_SERVICE_URL_AGENTICGOVERNANCEredirects both policy fetch and compensate POST; 5resolve_trace_idcases.uipath-runtime-pythontracer: fix lint #121/cli: pack should include mermaid files #123) to be refactored to consume the constructor-injected provider — separate PRs.🤖 Generated with Claude Code
Development Packages
uipath-core
uipath
uipath-platform