Skip to content

feat(governance): add track_event for custom telemetry to /runtime/log#1745

Open
viswa-uipath wants to merge 4 commits into
feat/adapter-fixfrom
feat/traces-api
Open

feat(governance): add track_event for custom telemetry to /runtime/log#1745
viswa-uipath wants to merge 4 commits into
feat/adapter-fixfrom
feat/traces-api

Conversation

@viswa-uipath

@viswa-uipath viswa-uipath commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds track_event to GovernanceService and UiPathPlatformGovernanceProvider for posting custom telemetry events to POST /agenticgovernance_/api/v1/runtime/log. The server forwards each event to App Insights as a customEvents row.

  • GovernanceService.track_event(*, event_name, data=None, operation_id=None) + async — POSTs { eventName, data? } to /runtime/log. eventName must be non-empty; data is included only when provided.
  • x-uipath-operation-id header — caller can supply operation_id to correlate events from one logical request (becomes App Insights operation_Id). When omitted, 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.track_event(*, ...) + async — thin delegate 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.

Design choices

  • Public ergonomic signature, matching 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_id fallback to resolve_trace_id(), not required — events emitted from inside an OTel-traced agent flow automatically get the canonical trace id as their operation_Id. Callers in background pools (where OTel context is thread-local and lost) pass operation_id explicitly; callers with neither leave App Insights to assign one per event.
  • Reuses _build_org_scoped_requestUIPATH_SERVICE_URL_AGENTICGOVERNANCE override + routing-header injection work for /runtime/log without extra plumbing. Override redirects to {override}/api/v1/runtime/log and replaces the platform router's tenant/account headers.
  • No new core protocoltrack_event is platform-specific telemetry, not a runtime contract. Adding a protocol can be done later if uipath-runtime consumers need to swap in fakes.
  • @traced(name="governance_track_event", ...) on both sync/async — matches the trace span names already used for retrieve_policy and compensate.

Out of scope (follow-ups)

  • A core protocol for track_event if/when a runtime consumer needs constructor-injected fakes.

Test plan

  • ruff check . / ruff format --check . — clean
  • mypy src testsSuccess: no issues found in 201 source files
  • 1301 passed, 7 skipped (LLM creds), 4 deselected — full uipath-platform suite
  • 12 new tests specifically for track_event:
    • TestTrackEvent (8): name-only payload; data included when provided; caller operation_id header; trace-id fallback via resolve_trace_id(); caller value wins over fallback; header omitted when no source resolves; UIPATH_ORGANIZATION_ID missing raises ValueError; HTTP error raises EnrichedException.
    • TestTrackEventAsync (1): async variant — payload shape + trace-id fallback.
    • TestServiceUrlOverride (+1): UIPATH_SERVICE_URL_AGENTICGOVERNANCE redirects /runtime/log and preserves x-uipath-operation-id.
    • TestDelegation (+2): provider delegation for sync + async.

🤖 Generated with Claude Code

Development Packages

uipath-platform

[project]
dependencies = [
  # Exact version (copy-paste ready):
  "uipath-platform==0.1.76.dev1017456959",

  # Any version from this PR (uncomment to use a range instead):
  # "uipath-platform>=0.1.76.dev1017450000,<0.1.76.dev1017460000",
]

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

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

uipath-core

[project]
dependencies = [
  # Exact version (copy-paste ready):
  "uipath-core==0.5.23.dev1017456957",

  # Any version from this PR (uncomment to use a range instead):
  # "uipath-core>=0.5.23.dev1017450000,<0.5.23.dev1017460000",
]

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

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

uipath

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

  # Any version from this PR (uncomment to use a range instead):
  # "uipath>=2.11.12.dev1017450000,<2.11.12.dev1017460000",
]

[[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" }
uipath-platform = { index = "testpypi" }

[tool.uv]
override-dependencies = ["uipath-platform==0.1.76.dev1017456959"]

Copilot AI review requested due to automatic review settings June 24, 2026 06:57
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-integrations labels Jun 24, 2026

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

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, with operation_id falling back to resolve_trace_id().
  • Add UiPathPlatformGovernanceProvider.track_event() / track_event_async() delegation methods.
  • Bump uipath-platform version 0.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.

viswa-uipath added a commit that referenced this pull request Jun 24, 2026
…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>
@viswa-uipath viswa-uipath added the build:dev Create a dev build from the pr label Jun 24, 2026
viswa-uipath and others added 4 commits June 25, 2026 08:39
…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>
@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 Jun 25, 2026
@github-actions github-actions Bot added test:uipath-runtime and removed build:dev Create a dev build from the pr labels Jun 25, 2026
@viswa-uipath viswa-uipath changed the base branch from main to feat/adapter-fix June 25, 2026 14:23
@viswa-uipath viswa-uipath added the build:dev Create a dev build from the pr label Jun 25, 2026
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

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 →

viswa-uipath added a commit that referenced this pull request Jun 26, 2026
…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>
viswa-uipath added a commit that referenced this pull request Jun 27, 2026
…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>
@viswa-uipath viswa-uipath force-pushed the feat/adapter-fix branch 2 times, most recently from 296d587 to c1ae046 Compare June 27, 2026 09:57
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 test:uipath-runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants