Observe¶
This page explains how AgentOps turns agent observability into release evidence and regression coverage. Foundry and Azure Monitor produce the runtime signal; AgentOps reads that signal so readiness reflects what is actually happening in production, not just what passed in CI.
Where the signal comes from¶
Foundry gives you the runtime view of an agent: traces, conversations, spans, latency, and model calls per run. Behind that view, Foundry emits telemetry to Azure Monitor / Application Insights, where requests, errors, and evaluation events are stored and queryable.
AgentOps does not replace either surface. It reads them so the same runtime truth feeds the readiness story alongside eval results and Doctor findings.
What AgentOps reads¶
AgentOps connects to Application Insights through
APPLICATIONINSIGHTS_CONNECTION_STRING. When a Foundry project endpoint is set,
AgentOps first tries to auto-discover the project's App Insights resource and
falls back to that connection string when discovery is not available.
Multi-project Observe¶
Observe can aggregate multiple Foundry resources and projects without hiding
where a value came from. The local workspace project is the default. An
operator may explicitly select project ARM resource IDs or widen the boundary
to one Foundry account, resource group, or subscription. The hosted application
stores that choice as one versioned AGENTOPS_OBSERVE_SCOPE setting; it does
not infer "all projects in the deployment resource group."
Discovery resolves each project's credential-free connection metadata to Application Insights and its backing Log Analytics workspace. Shared workspaces are queried once while retaining every Foundry/project origin. Observe limits a request to ten telemetry sources, applies filters before aggregation, and keeps successful source results when another source is denied, throttled, or times out.
The standard six views share the same explicit filter state:
- Overview shows bounded aggregate activity, latency, errors, and observed token usage.
- Agents attributes observed activity to standard
gen_ai.*identifiers. Every row includes itssource_id, so the same agent observed through two telemetry sources remains distinguishable. - Models and usage shows model/provider attribution and observed tokens, not billing or quota.
- Tools lists each observed tool by telemetry source, agent, and tool name, with invocation and failure counts, last-seen time, and p95 latency. Missing latency is shown as not measured, never as zero; tool token fields are intentionally not reported.
- Runs lists correlated agent executions by telemetry source and agent,
with the correlation key and kind (
conversationortrace), range-scoped start and duration, status, turns, failed turns, tool invocations, tool failures, and observed input/output token totals. Missing token totals are shown as not available, never as zero. - Telemetry coverage explains readable, empty, denied, unconfigured, unattributed, protected, timed-out, and partial sources.
When AGENTOPS_COST_MODEL contains a valid version 1 model, a seventh
Cost view appears. Cost uses its configured period boundaries and component
selectors instead of the shared Observe time, source, project, model, tool, and
run filters. This prevents a display filter from silently changing an allocation
denominator.
The default range is 24 hours. Draft filters do not query until Apply is
selected. Applied filters are bookmarkable in the URL, refresh every five
minutes, and may be refreshed manually. Raw trace content is never part of that
URL or browser persistence. Alongside the existing agent, model, and time-range
filters, Tools accepts tool_name and Runs accepts run_key. Both only
narrow results; blank values are rejected, and values are escaped before they
reach telemetry queries.
Large inventories¶
Observe keeps large inventories responsive by separating telemetry collection from table navigation. One bounded Azure Monitor collection builds a normalized aggregate of at most 5,000 rows. Agents, Models, Tools, Runs, and Coverage then search, sort, and paginate that aggregate through the Observe API, returning at most 100 rows per page. Changing the page, search text, sort column, or sort direction does not issue another Azure Monitor query.
Discovery is cached for 15 minutes. Normalized aggregates stay fresh for two minutes and can be served stale for up to five additional minutes while one background refresh runs. Single-flight coordination prevents concurrent requests for the same scope from duplicating discovery or telemetry work, and source batches use bounded concurrency. An explicit refresh bypasses reusable view data. Protected trace content and delegated user-level results remain excluded from shared caches.
Every response reports discovery, Azure Monitor, normalization, and total
durations. The HTTP endpoint exposes the same stages in Server-Timing, together
with cache hit, miss, bypass, or stale state, so operators can distinguish slow
discovery from a slow telemetry query.
Allocate declared billed totals¶
The Cost view is an operational allocation of totals supplied by an operator.
AgentOps does not call Azure Cost Management, discover invoices, apply rate
cards, or infer credits. Removing AGENTOPS_COST_MODEL hides Cost and leaves
every other Observe view unchanged.
Configure the model as bounded JSON before starting Cockpit:
$env:AGENTOPS_COST_MODEL = @'
{
"version": 1,
"periods": [{
"id": "2026-08",
"starts_at": "2026-08-01T00:00:00Z",
"ends_at": "2026-09-01T00:00:00Z",
"components": [{
"id": "gpt-ptu-prod",
"type": "provisioned_throughput",
"billing_boundary": {
"kind": "resource",
"value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/ai-prod/providers/Microsoft.CognitiveServices/accounts/foundry-prod"
},
"billed_source": "August provisioned-throughput commitment",
"billed_total": "12000.00",
"currency": "USD",
"currency_minor_units": 2,
"allocation_model": "commitment",
"allocation_key": "weighted_tokens",
"fallback_key": "total_tokens",
"token_weights": {
"input_tokens": "1",
"output_tokens": "4",
"cache_read_tokens": "0.25"
},
"usage_match": {
"deployments": ["gpt-prod"]
}
}]
}]
}
'@
agentops cockpit
The value is read once at process startup, is limited to 32 KiB, and rejects unknown or secret-shaped fields. Restart Cockpit after changing or removing it. Malformed configuration disables only Cost and returns a non-sensitive validation error for direct Cost requests.
Component compatibility and evidence¶
Each component declares one total, currency, precision, billing boundary, and compatible usage key:
| Component | Preferred allocation evidence | Allowed fallback |
|---|---|---|
| Provisioned throughput | Weighted or total tokens | Total tokens when weighted is preferred |
| Standard model | Weighted or total tokens | Total tokens when weighted is preferred |
| Search, grounding, content safety, storage | Tool invocations | None |
| Hosted or customer compute | Active session seconds | None |
| Pay-as-you-go or prepaid credits | Directly reported credits or explicit configured credit events | Explicit configured credit events when credits are preferred |
Weighted tokens use only configured weights and directly observed token classes.
A missing class remains missing; it is never derived from another total. Direct
credits take precedence over credit-event fallback, and credit events count only
explicitly configured operation names. No event-to-credit rate is inferred.
usage_match narrows eligible observations by declared source, project, agent,
deployment, model, tool, or runtime values.
Every amount retains its billed source and boundary, selected allocation method, observed numerator and denominator, observation window, source counts, calculation time, freshness, and confidence. Confidence is:
- high when required telemetry is complete;
- medium when an explicit fallback is used;
- low when telemetry is partial;
- unavailable when no usable denominator exists.
Exact reconciliation and truthful gaps¶
AgentOps allocates with decimal arithmetic in integer currency minor units. A deterministic largest-remainder step uses the normalized consumer key to break ties. For every component and currency:
Agent, tool, and run breakdowns are alternative reconciliations of the same
billed pools; never add them together. Different currencies and minor-unit
precisions remain separate. Applying cost_agent_key happens after allocation:
hidden rows move to omitted_allocated_amount without changing denominators or
the original component allocation.
The UI and API keep these states distinct:
- Not configured: no declared total exists; this is not zero spend.
- Observed zero: telemetry explicitly reported zero.
- Unattributed: usage exists but lacks the selected agent, tool, or run key.
- Unallocated: a declared total has no usable observed denominator.
- Omitted: bounded or filtered allocated rows are excluded from display but remain in reconciliation summaries.
- Partial: one or more telemetry sources failed or reported incomplete dimensions; successful bounded results remain visible.
The hosted deployment may propagate the model as the allowlisted, non-secret
AGENTOPS_COST_MODEL App Service setting. It adds no Azure resource, identity,
role assignment, billing permission, or write permission. Existing aggregate
queries continue to use only Reader and Log Analytics Reader.
Operational cost allocation from declared billed totals and observed usage; not an invoice or billing-accurate charge.
Runtime attribution and source identity¶
source_kind now identifies the runtime more precisely. The accepted values
are foundry_hosted, foundry_prompt, external_registered,
external_unregistered, copilot_studio, and unknown. unknown means the
available telemetry cannot classify the runtime; it is an expected attribution
outcome, not an error.
This replaces the former foundry, external, and unknown contract. The
mapping is not one-to-one:
| Old value | New value(s) | Notes |
|---|---|---|
foundry |
foundry_hosted or foundry_prompt |
Split by runtime. |
external |
external_registered or external_unregistered |
Split by project registration. |
| — | copilot_studio |
New classification with no predecessor. |
unknown |
unknown |
Unchanged fallback when telemetry is insufficient. |
The old and new values are not emitted together. Consumers must update to the
refined values. source_id is also present on Agents, Tools, and Runs rows;
it identifies the telemetry source that produced the row and prevents rows from
different sources from being treated as one observation.
Runs are scoped to the selected window¶
Runs describe only activity observed in the selected time range. If a run began
before the range, its reported start, duration, turns, failed turns, and status
cover only the in-window activity and it is not marked as truncated at the
leading edge. A failure before the range is therefore not visible and an
otherwise settled row can report succeeded. The in_progress status instead
covers the trailing edge: activity near the range end may still be settling.
Truthful source and dimension states¶
Observe does not turn missing evidence into numeric zero. Every response carries source attribution, refresh time, query duration, source counts, partial failures, and dimension coverage. "Last seen" means observed activity in the selected range, not deployment or lifecycle status.
| State | Meaning |
|---|---|
| Available | The source/dimension was readable and reported values. |
| No data | The source was readable and had no matching rows in the selected range. |
| Not configured | No linked telemetry source was found. |
| Inaccessible | The active identity could not read the source. |
| Not reported | Rows existed but the requested semantic dimension was absent. |
| Partial | Some bounded sources succeeded and others did not. |
| Error / timed out | The source failed or exceeded its query budget. |
| Protected or unavailable | Protected content could not be proven readable, including ambiguous zero-row results. |
Aggregate access versus trace content¶
The shared UAMI reads discovery and aggregate telemetry with Reader and
Log Analytics Reader. It never receives Privileged Monitoring Data Reader.
Raw AppGenAIContent loads only after an explicit trace-detail action and uses
the signed-in user's delegated Azure Monitor permission through OBO, correlated
by source scope, TraceId, and SpanId. There is no legacy-field fallback.
Trace-content responses use Cache-Control: no-store; raw values never enter
shared caches, URLs, browser storage, telemetry, diagnostics, or deployment
artifacts. See Operate
and Deploy the hosted Cockpit.
Local developer versus hosted authentication¶
Cockpit authenticates one of two ways, chosen automatically by run mode.
Local mode (agentops cockpit on a developer workstation) uses your own
ambient Azure sign-in through DefaultAzureCredential(process_timeout=30) —
the Azure CLI, VS Code, or environment credential you already have. It requires
none of the hosted identity configuration: AGENTOPS_TENANT_ID,
AGENTOPS_APPLICATION_CLIENT_ID, and AGENTOPS_UAMI_CLIENT_ID may all be
unset. agentops cockpit therefore starts against any discoverable project
scope without hosted identity provisioning. The process_timeout=30 override is
mandatory on Windows, where the SDK's 10-second default times out the az.cmd
cold start.
Because local mode signs in as the developer rather than a hosted end user, it has no delegated end-user identity to act on behalf of. Aggregate discovery and telemetry reads work normally, but user-delegated views are reported as unavailable rather than emulated: the per-user attribution query and the protected trace-content read each return an actionable "unavailable in local developer mode" diagnostic (HTTP 409) instead of fabricating a delegated principal. Use the hosted Cockpit with Easy Auth for those views.
Hosted mode (the deployed Cockpit web application) keeps the full hosted chain
described above: a shared user-assigned managed identity for aggregate reads and
a fresh signed-in-user On-Behalf-Of credential for every delegated per-user and
trace-content read. It requires AGENTOPS_TENANT_ID,
AGENTOPS_APPLICATION_CLIENT_ID, and AGENTOPS_UAMI_CLIENT_ID; a missing value
fails fast with an actionable configuration error.
Enable user and department attribution¶
Attribution is opt-in. Set AGENTOPS_ATTRIBUTION_CONFIG on the Cockpit
deployment and restart it. An enabled version 1 configuration requires a stable,
random deployment namespace, a generation of at least 1, and up to 100
department definitions. Each definition maps deployment-scoped pseudonymous
user keys or group object IDs to one department. The setting is non-secret but
privacy-sensitive; keep its value out of source control, logs, tickets, and
release artifacts. Deployment preview shows only its enabled state, generation,
fingerprint, department-definition count, user-key entry count, and group-ID
entry count.
Bootstrap mappings from the protected Users view:
- Sign in as an operator who can read the selected telemetry scope directly.
- Open Users using the delegated view and copy only the generated
usr1.g<generation>.<sha256>key into the appropriate department definition. - Alternatively, configure a group object ID already present in the signed-in principal's validated claims. Group mapping applies only to that exact principal; AgentOps does not query Microsoft Graph or enrich identities.
- Preview the deployment, review the widened delegated-data warning, confirm, and restart Cockpit.
Only non-empty UserAuthenticatedId and OpenTelemetry enduser.id values are
eligible identity sources. UserId, enduser.pseudo.id, session, device,
browser, network, prompt, and behavioral values are never identity fallbacks.
Conflicting eligible aliases are ambiguous and remain unattributed.
Access, coverage, and privacy boundary¶
Department aggregates that cannot identify a singleton may use the deployment
identity and shared aggregate cache. Every individual view, user filter, and
singleton-department result uses a fresh signed-in-user OBO credential, bypasses
shared caches, and returns Cache-Control: private, no-store. Missing delegated
access or direct Log Analytics RBAC fails closed without retrying as the
deployment identity. Enabling attribution adds no role, Graph permission,
directory read, write capability, or secret; the deployment identity retains
only Reader and Log Analytics Reader, while delegated requests reuse Azure
Monitor Data.Read.
Coverage is reported per source and measure. It distinguishes available, partial, not reported, inaccessible/protected, ambiguous, and error states. Unmapped and ambiguous consumption remains in unattributed totals, and a failed source does not erase successful aggregate evidence from another source. Cost attribution appears only when an existing declared cost allocation is available; it does not change billed totals, allocation denominators, or reconciliation. If the signed-in token reports group-claim overage while group mappings are configured, attribution performs no Microsoft Graph or directory lookup. Coverage is marked partial with fixed guidance to use explicit user mappings or sign in with group claims within the supported token limit.
Pseudonymous keys and opaque filters are deterministic, linkable personal data, not anonymous data. A namespace limits cross-deployment correlation but is not a secret and does not prevent guessing low-entropy identities. Raw identities are shown only in the current delegated Users response. Mapping values, raw identities, user rows, group IDs, and filter tokens are excluded from shared caches, application logs, Doctor output, deployment journals, and release evidence. Aggregate readiness status, coverage states, generation, configuration fingerprint, and non-identifying counts remain available.
Rotate or disable attribution¶
Rotation is explicit, never time-based: generate a fresh random namespace,
increment generation, rebuild all user mappings from the protected Users view,
preview and confirm the deployment, then restart Cockpit. Existing keys and
bookmarked filters fail closed after rotation; there is no grace period or
previous-generation lookup.
To disable attribution, remove AGENTOPS_ATTRIBUTION_CONFIG or deploy a valid
configuration with enabled: false, then restart Cockpit. Attribution routes,
controls, filters, and coverage disappear while the existing Observe views and
aggregate access remain unchanged. Correct an invalid setting or remove it;
invalid attribution configuration does not take unrelated Cockpit diagnostics
offline.
Attribution acceptance protocols¶
Use synthetic identities and a fixed dataset for both protocols. Do not export the view.
For the SC-005 operator check, recruit at least ten representative operators who have not seen the answer. Give every participant the same unfiltered department Usage view and these instructions only: identify the highest-consuming department, apply its department filter, copy the resulting URL, open that URL in a new private window while signed in, and confirm the same department and time range. Start one stopwatch when the unfiltered view is shown and stop it after the restored view is confirmed. Record participant ID, start/end timestamps, selected department correctness, restored-filter correctness, elapsed seconds, and pass/fail in the acceptance record. A participant passes only when both answers are correct and elapsed time is at most 120 seconds. SC-005 passes when at least 90% pass; retain the aggregate record, not identities or copied URLs.
For SC-006, the standard offline scope is one project, representative coverage for three telemetry sources, 200 synthetic users, and the maximum 100 departments. Run 20 Usage and 20 Cost display samples after one warm-up and record each elapsed duration from receipt of the bounded aggregate response through completed HTML rendering. Compute p95 by nearest rank (the 19th sorted sample of 20) separately for Usage and Cost. Both p95 values must be at most five seconds. The repository acceptance test uses deterministic synthetic responses and requires no Azure access:
This controlled check detects application-side display regressions. Before a release, repeat the same 20-sample protocol in the target connected environment to include discovery, Azure Monitor query, network, and browser costs.
Telemetry from CI runs
Generated eval and Doctor workflows install OpenTelemetry support.
Eval runs emit agentops.eval.* spans and scheduled Doctor runs emit
agentops.agent.finding.* spans, both of which the Cockpit can deep-link
into Azure Monitor Logs.
OpenTelemetry spans and semantic conventions¶
An OpenTelemetry trace is a tree of spans. Each span represents one unit of
work and records a name, start and end timestamps, status, attributes, and its
parent/child relationship to other spans. The OpenTelemetry generative AI
semantic conventions give those spans consistent names and gen_ai.*
attributes, so the same telemetry remains meaningful in Foundry and Azure
Monitor.
| Work | Span and operation | Key attributes |
|---|---|---|
| Agent invocation | invoke_agent <agent-name>; gen_ai.operation.name=invoke_agent |
gen_ai.agent.name, gen_ai.agent.id, gen_ai.conversation.id |
| Model call | chat <model>; gen_ai.operation.name=chat |
gen_ai.provider.name, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
| Tool execution | execute_tool <tool-name>; gen_ai.operation.name=execute_tool |
gen_ai.tool.name |
Domain attributes such as helpdesk.ticket.queue can add business context, but
they complement rather than replace the standard gen_ai.* attributes.
Keep sensitive content out of telemetry
Do not record secrets, tokens, or personal data in span attributes.
gen_ai.input.messages, gen_ai.output.messages, and tool arguments or
results can contain sensitive content, so omit, minimize, or redact them.
Configure an agent to emit telemetry¶
Agents hosted in Foundry receive server-side tracing after you connect an Application Insights resource to the project. If you own application code around the agent call, add client-side instrumentation to capture that custom logic as well.
Install the Azure Monitor OpenTelemetry distribution:
Set APPLICATIONINSIGHTS_CONNECTION_STRING in the process environment; using
an application setting or secret reference is recommended in production. Then
configure Azure Monitor once during application startup:
from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry.sdk.resources import Resource
configure_azure_monitor(
resource=Resource.create({"service.name": "helpdesk-agent"}),
)
Do not hardcode the connection string in source. After startup, run the agent and verify a new trace in Foundry > Agents > Traces or Application Insights > Investigate > Agents (Preview). See the official Foundry tracing setup for the complete connection, permissions, and verification flow.
Instrument a custom Python agent¶
Once Azure Monitor is configured, application code can reuse the global tracer provider without depending on an agent framework:
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
tracer = trace.get_tracer("contoso.helpdesk.agent")
def lookup_ticket(ticket_id: str) -> str:
return f"Ticket {ticket_id} is queued for review."
def run_agent(ticket_id: str, conversation_id: str) -> str:
agent_attributes = {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.name": "helpdesk-agent",
"gen_ai.agent.id": "helpdesk-agent-v1",
"gen_ai.conversation.id": conversation_id,
}
with tracer.start_as_current_span(
"invoke_agent helpdesk-agent",
attributes=agent_attributes,
record_exception=False,
set_status_on_exception=False,
) as agent_span:
try:
with tracer.start_as_current_span(
"execute_tool lookup_ticket",
attributes={
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.name": "lookup_ticket",
},
record_exception=False,
set_status_on_exception=False,
) as tool_span:
try:
ticket = lookup_ticket(ticket_id)
except Exception as exc:
tool_span.set_attribute("error.type", type(exc).__name__)
tool_span.set_status(Status(StatusCode.ERROR))
raise
agent_span.set_status(Status(StatusCode.OK))
return f"Helpdesk response: {ticket}"
except Exception as exc:
agent_span.set_attribute("error.type", type(exc).__name__)
agent_span.set_status(Status(StatusCode.ERROR))
raise
The span context managers preserve the parent/child relationship and finish
each span with its measured duration automatically. The example disables
automatic exception recording and records only error.type, avoiding exception
messages, stack traces, and tool arguments in telemetry.
For a more specific policy-span implementation, see the
acs_middleware.py example.
The official guide to
trace structure and semantic conventions
describes the same agent, model, and tool span hierarchy in detail.
Traces as evaluation signal¶
A single trace shows what one request did. The value for release readiness comes
from reading many traces at once: latency percentiles, error rates, and the
evaluation results Foundry records as gen_ai.evaluation.result events.
The Doctor turns this into findings. It reads App Insights for p95 latency and error rate, and it reports when telemetry is connected but silent, so a project with no monitoring does not look healthy simply because nothing is being graded.
Real telemetry surfaces production findings
Because the Doctor reads live runtime data, it can surface latency or error findings from your own production traffic, separate from the eval gate. That is intended: a real release should investigate latency and errors before promoting, even when the candidate's eval scores pass.
Run from your coding agent¶
Install the AgentOps skills so your coding agent can read telemetry and investigate production health.
The skills that map to observability are:
| Skill | What it helps with |
|---|---|
agentops-agent |
Watchdog analysis of production health and latency spikes. |
The Observe dashboard¶
Observe renders as a self-contained operational dashboard that is visually part of the AgentOps Cockpit. It shares one design system with Cockpit instead of theming itself independently.
One shared theme¶
Cockpit and Observe read the same canonical design tokens from
src/agentops/agent/ui_theme.py: colors, surfaces, borders, typography, and the
aos-* layout primitives (header, shell, cards). Observe imports those tokens
rather than defining its own palette, so both surfaces look like one product.
Theme selection is explicit. The page ships a deliberate dark theme by
default (<html data-theme="dark">) and a matching light theme
([data-theme="light"]), and an in-page toggle switches between them. Observe
does not use a bare @media (prefers-color-scheme: dark) rule, which is what
previously let it drift to white while Cockpit stayed dark. The toggle keeps its
choice in memory only for the current page — it never writes localStorage,
sessionStorage, cookies, or any other browser persistence, preserving the
privacy guarantees.
Executive overview and first-class trends¶
The Overview is an executive summary: compact KPI cards with a value, a
direction-aware delta chip (the direction is shown with a glyph, not color
alone), a short caption, and an inline sparkline. Below the cards, invocation,
failure, latency, token, and coverage trends render as first-class charts with
thin trend lines, a subtle grid, restrained legends, exact-value tooltips, and
an accessible area gradient. Charts are responsive and every chart is
screen-reader accessible (role="img", an aria-label, an SVG <title>/<desc>,
and a visually hidden data <table>).
Filters stay compact and visually subordinate to the summary. The Agents, Models, Tools, Runs, Costs, Attribution, and Coverage views are clear drill-down tables. Status and classification badges use filled semantic surfaces, high-contrast borders, 12-pixel bold text, and the same minimum height as nearby controls in both themes, avoiding the thin low-resolution treatment used previously.
Intentional states¶
Every view can render six deliberate states through one shared
render_state_panel helper: loading, empty, partial,
permission-denied, disconnected, and error. Each state uses a
non-color glyph plus text (so meaning does not depend on color), the correct
ARIA role (role="alert" for errors, role="status" otherwise, aria-busy
while loading), and a data-observe-state hook for tests.
Preserved guarantees¶
The redesign is presentation-only. It preserves accessibility (ARIA labels,
roles, table semantics, and non-color status encoding), privacy (no receiver
addresses, PII, secrets, or raw trace content in the markup), bounded queries,
URL-driven filters that round-trip through the allow-listed query keys, and
protected-content handling. The HTML remains fully self-contained: no CDN,
<link>, or network fetch happens at render time, so Observe works offline and
in locked-down environments.
Visual regression tests¶
Because Playwright and a real browser are not guaranteed in CI, visual
regression is enforced as deterministic HTML/CSS snapshot tests in
tests/unit/test_observe_ui_visual.py. Fixed fixture data renders the page and
stylesheet with no clock, random, or UUID input, and the output is compared
against committed goldens under tests/unit/__snapshots__/
(observe_overview.html, observe_styles.css). The same file also asserts
theme parity with ui_theme.py, that all six states render, chart and KPI
accessibility, privacy invariants, and URL filter round-trips.
Regenerate the goldens after an intentional visual change:
$env:PYTHONPATH = "src"
$env:AGENTOPS_UPDATE_SNAPSHOTS = "1"
python -m pytest tests/unit/test_observe_ui_visual.py -q
Remove-Item Env:\AGENTOPS_UPDATE_SNAPSHOTS
Review the regenerated snapshot diff before committing, then run the suite again without the environment variable to confirm it passes against the new goldens.
Next¶
Act on the signal over time on the Operate page, feed passing evidence back into the gate on the Ship page, or harden the dataset on the Evaluation page.