feat(governance): add track_event for custom telemetry to /runtime/log#1745
feat(governance): add track_event for custom telemetry to /runtime/log#1745viswa-uipath wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a platform-specific telemetry hook to the uipath-platform governance surface so runtime consumers can emit custom events to the agentic governance ingress (POST .../api/v1/runtime/log), with optional correlation via x-uipath-operation-id.
Changes:
- Add
GovernanceService.track_event()/track_event_async()to POST{eventName, data?}to/runtime/log, withoperation_idfalling back toresolve_trace_id(). - Add
UiPathPlatformGovernanceProvider.track_event()/track_event_async()delegation methods. - Bump
uipath-platformversion0.1.73 → 0.1.74(and lockfile).
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath-platform/src/uipath/platform/governance/_governance_service.py | Adds the new /runtime/log client methods and operation-id header behavior. |
| packages/uipath-platform/src/uipath/platform/governance/_governance_provider.py | Exposes track_event through the provider adapter via thin delegation. |
| packages/uipath-platform/tests/services/test_governance_service.py | Adds sync/async tests covering payload shape, operation-id behavior, override routing, and error paths. |
| packages/uipath-platform/tests/services/test_governance_provider.py | Adds delegation tests for the provider’s sync/async track_event methods. |
| packages/uipath-platform/pyproject.toml | Version bump to 0.1.74. |
| packages/uipath-platform/uv.lock | Lockfile update reflecting the version bump. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ockfile track_event / track_event_async now reject empty or whitespace-only event_name with a ValueError at call time instead of round-tripping the platform's own 4xx (per Copilot review on #1745). Documented in the Raises section and covered by parametrized tests on both the sync and async variants. Also regenerates packages/uipath/uv.lock so it tracks the bumped uipath-platform 0.1.74 — the prior CI run failed `uv sync --locked` because the lockfile still referenced 0.1.73. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f702886 to
73908ab
Compare
…tocol The adapter-registry plugin-discovery system in uipath-core was a parallel keyed-by-agent-type dispatcher for a fact already known statically: the runtime-factory registry has picked the framework by the time anything needs an "adapter". The two registries existed only to support an open-PR pattern (#125/#126) where the runtime layer sniffed the framework off an opaque agent. With that approach abandoned in favor of framework plugins wiring governance at their native seam (callback handlers, hook lists), the registry, the BaseAdapter abstraction, and their per-package entry-point group are all dead weight. Deletions - src/uipath/core/adapters/base.py — BaseAdapter + GovernedAgentBase. No internal consumers; the only external one (uipath-langchain PR #899) is unmerged and is being reshaped to consume EvaluatorProtocol directly via its factory. - src/uipath/core/adapters/registry.py — AdapterRegistry, get_adapter_registry, reset_adapter_registry, the `uipath.governance.adapters` entry-point group, and the side-effect discovery on first call. - tests/adapters/test_base.py and tests/adapters/test_registry.py. Kept - src/uipath/core/adapters/evaluator.py — EvaluatorProtocol is the one contract framework plugins still consume. Plugin factories accept an evaluator at create_runtime() time and wire it into their own callback seam. - tests/adapters/test_evaluator.py — protocol-conformance unchanged. Net diff: ~960 LOC removed. uipath-core 0.5.22 → 0.5.23. uv.lock files regenerated in uipath-core, uipath-platform, and uipath (workspace editable-path deps, so they pick up 0.5.23 immediately). Verified: ruff clean, mypy clean (45 source files), 230 passed + 1 skipped in uipath-core's test suite. Monorepo grep for AdapterRegistry / BaseAdapter / GovernedAgentBase / get_adapter_registry / reset_adapter_registry returns zero hits outside the deleted files across .py/.toml/.md/.rst/.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes doc item 1.2 — every ``evaluate_*`` method on ``EvaluatorProtocol`` returned ``-> Any``, forcing callers to downcast to the type they already knew the concrete evaluator was returning. The concrete ``GovernanceEvaluator`` in uipath-runtime-python already declares ``-> AuditRecord`` on each per-hook method, so narrowing the protocol contract is structurally compatible — no behavior change, no downstream code change required. Narrows the six evaluate_* return types (before_agent / after_agent / before_model / after_model / tool_call / after_tool) from Any to AuditRecord, imports the type from uipath.core.governance.models, and refreshes the class docstring (was claiming the protocol is intentionally Any because the audit record "lives in the plugin package" — but AuditRecord lives right here in uipath-core). Verified - ruff clean, mypy clean (45 source files), 230 passed + 1 skipped in uipath-core. - uipath-runtime-python's test suite (357 passed + 1 skipped) keeps green when this version of uipath-core is installed — the protocol- conformance tests in test_evaluator.py still pass because the concrete GovernanceEvaluator was already returning AuditRecord from every evaluate_* method. Rides on the same 0.5.23 version bump as the previous commit — both changes ship together as one public-surface change on uipath-core. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unblocks architecture-review §2.4 on the uipath-runtime side. The prescription there is to hoist the policy fetch from the runtime-layer ``PolicyLoader`` (which today spins a daemon thread inside an async runtime and blocks on ``threading.Event.wait(timeout=10s)``) up to the async host: the CLI calls ``await provider.get_policy_async(ctx)`` itself, builds the ``PolicyIndex``, and passes the resolved index + mode into ``GovernanceRuntime``. The runtime collapses to a pure, synchronous-to-construct decorator — no thread, no Event, no ``is_conversational`` in the ctor. For that to type-check on the runtime side, the structural ``GovernancePolicyProvider`` Protocol in uipath-core needs to declare ``get_policy_async``. The concrete platform provider (``UiPathPlatformGovernanceProvider``) already implements it; the contract was just lying about what providers expose. Changes - ``GovernancePolicyProvider`` now declares both ``get_policy`` and ``async get_policy_async``. Both required (the platform impl ships both today, and the doc's recommended caller path is the async variant — sync stays for non-event-loop callers like integration tests and CLI tools). - ``_FakePolicyProvider`` in the conformance tests grew the async method and a separate ``async_calls`` recorder. - New ``test_policy_round_trip_async`` exercises the async path via ``@pytest.mark.asyncio`` and pins that the two entry points are independent (calling one doesn't touch the other's recorder). Verified - uipath-core: ruff clean, mypy clean (45 source files), 32 governance tests passed. - uipath-platform: protocol-conformance tests still pass (9 passed) — ``UiPathPlatformGovernanceProvider`` already exposed ``get_policy_async``, so the now-stricter protocol still accepts it structurally. No version bump — rides on the unreleased 0.5.23 that already carries PR #1761's §1.1 (adapter-registry deletion) and §1.2 (typed EvaluatorProtocol returns). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GovernanceService.track_event(*, event_name, data=None, operation_id=None) posts to /runtime/log, which the server forwards to App Insights as a customEvents row. Both sync and async variants are provided. event_name is validated client-side — empty or whitespace-only values raise ValueError before any URL/header work, so callers fail fast with a clear error instead of round-tripping the platform's 4xx. The optional operation_id becomes the x-uipath-operation-id header that the server stamps as App Insights operation_Id on every emitted event — events sharing an id are queryable together in KQL. When the caller omits operation_id, it falls back to resolve_trace_id() so events from the same agent trace correlate automatically; when no source resolves, the header is omitted and App Insights generates its own id per event. UiPathPlatformGovernanceProvider exposes thin track_event / track_event_async delegates so runtime consumers can emit events through the protocol-adapter surface without importing the platform service directly. Bumps uipath-platform 0.1.73 → 0.1.74. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73908ab to
8a03bce
Compare
|
🚨 Heads up:
|
…it empty Lets the runtime layer (uipath-runtime-python) stop carrying ``trace_id`` through ``UiPathGovernedRuntime`` / ``GuardrailCompensator``. The runtime emits compensation requests with ``trace_id=""`` and the platform fills in the canonical agent trace id at HTTP-call time via the existing ``resolve_trace_id()`` helper — same fallback ``track_event`` (PR #1745) already uses. uipath-core - ``GovernRequest.trace_id`` relaxes from required ``str`` to ``str = ""`` default. Docstring documents the platform-side self-resolve contract so wire callers know an empty value is legitimate. uipath-platform - ``GovernanceService._compensate`` / ``_compensate_async`` now call a new ``_resolve_request_trace_id()`` helper before the POST. When ``request.trace_id`` is empty the helper resolves via ``resolve_trace_id()`` (env → LLMOps external span → OTel current span). Caller-supplied values win — the runtime captures live OTel context across its background-pool hop via ``contextvars.copy_context()``, so when the worker calls ``provider.compensate(...)`` the platform-side resolver sees the agent's live span and returns the same canonical id. Caller-supplied non-empty trace ids continue to pass through unchanged. Tests - uipath-core governance suite: 32 passed. - uipath-platform governance service suite: 27 passed. - ruff + mypy clean on ``src/uipath/core/governance`` and ``src/uipath/platform/governance``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cb498ce to
903c35b
Compare
…it empty Lets the runtime layer (uipath-runtime-python) stop carrying ``trace_id`` through ``UiPathGovernedRuntime`` / ``GuardrailCompensator``. The runtime emits compensation requests with ``trace_id=""`` and the platform fills in the canonical agent trace id at HTTP-call time via the existing ``resolve_trace_id()`` helper — same fallback ``track_event`` (PR #1745) already uses. uipath-core - ``GovernRequest.trace_id`` relaxes from required ``str`` to ``str = ""`` default. Docstring documents the platform-side self-resolve contract so wire callers know an empty value is legitimate. uipath-platform - ``GovernanceService._compensate`` / ``_compensate_async`` now call a new ``_resolve_request_trace_id()`` helper before the POST. When ``request.trace_id`` is empty the helper resolves via ``resolve_trace_id()`` (env → LLMOps external span → OTel current span). Caller-supplied values win — the runtime captures live OTel context across its background-pool hop via ``contextvars.copy_context()``, so when the worker calls ``provider.compensate(...)`` the platform-side resolver sees the agent's live span and returns the same canonical id. Caller-supplied non-empty trace ids continue to pass through unchanged. Tests - uipath-core governance suite: 32 passed. - uipath-platform governance service suite: 27 passed. - ruff + mypy clean on ``src/uipath/core/governance`` and ``src/uipath/platform/governance``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
296d587 to
c1ae046
Compare



Summary
Adds
track_eventtoGovernanceServiceandUiPathPlatformGovernanceProviderfor posting custom telemetry events toPOST /agenticgovernance_/api/v1/runtime/log. The server forwards each event to App Insights as acustomEventsrow.GovernanceService.track_event(*, event_name, data=None, operation_id=None)+ async — POSTs{ eventName, data? }to/runtime/log.eventNamemust be non-empty;datais included only when provided.x-uipath-operation-idheader — caller can supplyoperation_idto correlate events from one logical request (becomes App Insightsoperation_Id). When omitted, falls back toresolve_trace_id()so events from the same agent trace correlate automatically. When no source resolves, the header is omitted and App Insights generates its own id per event.UiPathPlatformGovernanceProvider.track_event(*, ...)+ async — thin delegate so runtime consumers can emit events through the protocol-adapter surface without importing the platform service directly.Bumps:
uipath-platform0.1.73 → 0.1.74.Design choices
compensate(*, ...)—track_event(*, event_name, data=None, operation_id=None)follows the same kwarg-only pattern already established on the service; no request-object wrapper.operation_idfallback toresolve_trace_id(), not required — events emitted from inside an OTel-traced agent flow automatically get the canonical trace id as theiroperation_Id. Callers in background pools (where OTel context is thread-local and lost) passoperation_idexplicitly; callers with neither leave App Insights to assign one per event._build_org_scoped_request—UIPATH_SERVICE_URL_AGENTICGOVERNANCEoverride + routing-header injection work for/runtime/logwithout extra plumbing. Override redirects to{override}/api/v1/runtime/logand replaces the platform router's tenant/account headers.track_eventis platform-specific telemetry, not a runtime contract. Adding a protocol can be done later ifuipath-runtimeconsumers need to swap in fakes.@traced(name="governance_track_event", ...)on both sync/async — matches the trace span names already used forretrieve_policyandcompensate.Out of scope (follow-ups)
track_eventif/when a runtime consumer needs constructor-injected fakes.Test plan
ruff check ./ruff format --check .— cleanmypy src tests—Success: no issues found in 201 source filesuipath-platformsuitetrack_event:TestTrackEvent(8): name-only payload;dataincluded when provided; calleroperation_idheader; trace-id fallback viaresolve_trace_id(); caller value wins over fallback; header omitted when no source resolves;UIPATH_ORGANIZATION_IDmissing raisesValueError; HTTP error raisesEnrichedException.TestTrackEventAsync(1): async variant — payload shape + trace-id fallback.TestServiceUrlOverride(+1):UIPATH_SERVICE_URL_AGENTICGOVERNANCEredirects/runtime/logand preservesx-uipath-operation-id.TestDelegation(+2): provider delegation for sync + async.🤖 Generated with Claude Code
Development Packages
uipath-platform
uipath-core
uipath