ESRA — Operations
Audit, metrics, tracing, health and diagnostics. ESRA introduces no new pipeline for any of these — it emits into the platform's.
Audit
ESRA emits governance audit records with a dedicated action namespace, so its stages appear in the existing Call Flow trace and flow to external immutable audit targets with no new plumbing.
Retrieval:
| Action | Records |
|---|---|
esra.invoke |
invocation id, tenant, client, exposure, capability, configuration version, outcome, total duration, input/output/total token counts and model-call count (not gated on payload capture — counts, not content) |
esra.sources.resolve |
all four scope levels and every narrowing note |
esra.sources.select |
candidates, mandatory, policy-forced, model-selected, rejected, confidence |
esra.plan |
plans generated, mappings selected, queries per source, applied limits, model reference |
esra.plan.reject |
query id and reason code for every rejected query |
esra.provider.query |
source, provider, mapping, query id, duration, result count, error classification, retries, circuit state, cache hit |
esra.aggregate |
candidate count, deduplicated count, dedup ratio, source distribution |
esra.rerank |
rerank input count, fallback used, token usage |
esra.answer |
answer status, citation validation outcome |
esra.document.fetch |
document ref hash, source, offset, returned size |
Administrative actions are not currently audited. There is no governance-audit action namespace for
admin CRUD on sources, capabilities, exposures, stores or sync — connection tests, publication, rollback,
enable/disable and every other admin action against these screens emit no governance audit record today.
This is a real gap, not a design choice, and it is called out here rather than papered over: an
administrator's own changes to ESRA configuration currently leave no audit trail distinct from the
platform's generic entity-change logging (if any). The only audit record ESRA's admin surface writes is
the single hardcoded esra.preview action from Try & trace (see Diagnostics below), which records
that a query was previewed — it is unrelated to CRUD and does not fill this gap.
Capability invocation: a capability tool branches out of the mapping pipeline before the
request.transform / response.transform audits that would otherwise carry its MCP payloads, so the
Runtime writes its own capability.invoke record — the caller's tool arguments and the response they
received, captured under the same per-exposure flag and byte ceiling as every other capture and, for
ESRA, after the capability's own governance (masked fields masked, restricted fields absent).
The hot-path rule
Audit must never add latency or a failure mode to retrieval. Payload capture is conditional on a
per-exposure flag, writes are asynchronous, and a failed audit write never fails an invocation — the
same pattern the platform's tool.invoke already follows.
Never captured: connection strings, resolved secrets, generated SQL, raw provider errors. Prompts and result fragments are captured only under an explicit per-capability retention classification; a hash or redacted form is the default.
Warning messages carry a fixed string and, at most, a source name — never an id-to-host mapping and never error text.
Metrics
| Metric | Type | Dimension highlights |
|---|---|---|
esra_invocations_total |
counter | tenant, client, tool, outcome |
esra_invocation_duration_ms |
histogram (ms) | tenant, client, tool, outcome |
esra_stage_duration_ms |
histogram (ms) | tenant, stage, outcome |
esra_provider_queries_total |
counter | tenant, source, provider, outcome |
esra_provider_query_duration_ms |
histogram (ms, bucketed 5ms–30s, giving p50/p95/p99) | tenant, source, provider, outcome |
esra_provider_query_results |
histogram | tenant, source, provider, outcome |
esra_llm_tokens_total |
counter | tenant, task, model, token kind (prompt/completion) |
esra_stage_fallbacks_total |
counter | tenant, stage, fallback trigger |
Every metric carries the tenant dimension. Tenant is never omitted, including on failures and denials.
This is the exhaustive list of instruments ESRA currently defines. Selected-source count, query fan-out, deduplication ratio, rerank candidate count, per-source retry/timeout rate as a distinct metric, circuit-breaker openings, and ingestion lag/queue depth are not currently instrumented — none of those exist as counters or histograms in code today, however useful they would be. Some of that information is visible elsewhere (e.g. circuit state on the source grid, described under Circuit breaking below; per-stage detail in the invocation trace), just not as a metric.
What the model has cost
A durable running total per capability — input tokens, output tokens, invocation count, model-call count — is tracked separately from the point-in-time metrics above. It answers a question neither the metrics nor the invocation trace can: the metric counters report current spend and are aggregated away, and the trace is a read model over an audit trail that is scanned in a bounded window and purged on a retention schedule, so it can say what one run cost and never what a capability has cost in total.
It is reported on the Portal's capability list and detail screens, and summed across every capability — retired ones included — on the overview, since a retired capability's spend is still spent. Counting happens at the one point every model call passes through on its way out, so a call that timed out or was retried is billed exactly like one that succeeded. Where a connector reports no usage the count falls back to the platform's four-characters-per-token estimate, and the total says how much of it was estimated rather than presenting the two as the same kind of number. Accrual is off the hot path — buffered in memory and flushed by a background writer that coalesces by capability — on the same terms as audit.
Tracing
One span per stage, plus one per provider query, as children of the MCP invocation span. A single distributed trace therefore covers MCP invocation → policy evaluation → orchestration → selection → planning → each provider query → aggregation → reranking → audit dispatch.
Health
Contributed to the Runtime host: provider registration, service-LLM reachability, internal-store availability, configuration validity.
Source connectivity is probed on a schedule, not on the readiness path. A failing optional source degrades a retrieval; it does not fail readiness. The four-state model — healthy, degraded, unhealthy, disabled — is surfaced per source in the Portal.
The sweep runs as a scheduled background job and probes every published, enabled source. It exists because health is not decoration: scope resolution excludes an unhealthy source, so without it a database that went away overnight keeps being selected and keeps failing every invocation that selects it until an operator probes it by hand.
| Situation | Behaviour |
|---|---|
| A probe throws | that source is recorded unhealthy; the sweep continues |
A source is Degraded |
it stays in scope — searchable, slower or less accurate — and says so |
| The source is a draft | not probed; nothing depends on a draft's health |
| No provider serves it in this host | its recorded health is left untouched — a composition problem is not the source's fault |
| Nothing changed | recorded, not reported. A five-minute sweep that logged every pass would bury the pass that matters |
The sweep is configurable and can be switched off for a deployment whose sources sit behind a connection-rate-limiting gateway. The Portal then shows when the probe last actually ran, rather than implying the recorded value is current.
Circuit breaking
A separate signal from health above, and a faster one: health is a scheduled probe's opinion, checked
every few minutes; circuit state is the live resilience pipeline's view, updated the moment a source
starts or stops failing. A source can show Healthy from the last probe with an Open circuit, because
it started failing seconds after the probe ran.
Each source has its own circuit breaker: under a real outage, the breaker turns what would otherwise be
every concurrent query burning its own retry budget and timing out independently into fast, immediate
failures after an initial run of errors — until a single probing query decides the source has recovered.
Breaker behavior is configurable per source on the Ceilings tab and defaults to on. State (Closed /
Open / Half-open) is surfaced on the source grid and detail screen, refreshed on the same schedule as
the health sweep rather than instantaneously — a deliberate choice, so that surfacing circuit state never
adds a database write to a retrieval itself.
Result caching
Off by default, per source, on the same Ceilings tab: "Allow result caching" and a cache TTL (default 30 seconds, deliberately short — this collapses a burst of near-identical queries against a slow-changing source, not a data warehouse). Turning it on for a source whose content changes continuously just serves stale rows for no benefit; it is a setting for a reference catalog, not a ticketing system.
Results are cache-isolated per tenant, source, and caller, which is what makes this safe to turn on even
for a source using a claim-narrowed record filter — two callers who are entitled to see the same data may
share a cache entry, but a caller entitled to different data structurally cannot receive it, cache or not.
Only raw, pre-mask rows are ever cached — masking re-applies on every read, cache hit or not, so a
masking-policy change takes effect immediately rather than waiting out the TTL. Only Results-mode
queries are eligible; a grounded answer always regenerates from current evidence, never from a cached
result.
A cache hit still writes the invocation's audit record and stage trace — a cached answer is still a served
answer for audit purposes, marked so in the esra.provider.query record's cacheHit dimension. Only the
round-trip to the source itself was skipped.
Index and schema advisories
The scheduled health probe also flags conditions that make a mapping searchable but suboptimal, never
conditions that make it unsearchable — those already surface as Degraded/Unhealthy above. Three
closed-set advisory findings: a PostgreSQL index that exists but is a plain column rather than an
expression matching the mapping's computed search vector, so it cannot actually accelerate the query; a
matching expression index that was built against a text-search configuration the mapping no longer names;
and, on either engine, a claim-narrowed record filter's column with no index covering it.
Advisory means exactly that: never a blocker, never a contributor to a source's health state, never a
reason a source drops out of scope. The source grid shows a count of these findings as an amber dot,
distinct from the health badge, since a source can be perfectly Healthy and still carry one — "reachable
but suboptimal" is not a state the health badge alone can express. Each finding on the source detail
screen's findings panel names the affected relation and column and states the concrete remediation as text
an operator can hand to their DBA; ESRA never runs the DDL itself.
Errors and Warnings
Closed sets. Nothing outside them reaches a caller.
Connection failures name a remedy
Four failures happen before the driver is involved — an unallowlisted host, an unresolvable secret, an
unparseable template, and a source configured for an isolation level its database forbids. Each carries
its own classification rather than falling through to UNKNOWN, and the Portal's connection test states
what to change:
| Classification | What to do |
|---|---|
HOST_NOT_ALLOWED |
add the host to the tenant's outbound allowlist; the database was never contacted |
SECRET_UNRESOLVED |
re-bind the username or password secret — SECURITY_MANAGE work |
CONNECTION_TEMPLATE_INVALID |
fix the template; it could not be parsed, or names no server |
SNAPSHOT_ISOLATION_UNAVAILABLE |
enable ALLOW_SNAPSHOT_ISOLATION on the database, or set the source to read committed |
Anything genuinely unrecognised still reports UNKNOWN. A confident wrong answer sends an operator
further from the cause than an honest admission of ignorance.
A fifth, CIRCUIT_OPEN, is not a connection failure at all: it means the query never reached the
driver — the source's circuit breaker was already open from prior failures and failed the call fast. It
is permanently excluded from the retryable set, since retrying around an open circuit would defeat the
point of tripping it; the remedy is to look at what opened the circuit, not to retry.
Errors — the invocation fails:
| Code | Meaning |
|---|---|
ESRA_INVALID_REQUEST |
prompt missing or empty, filter malformed |
ESRA_RESPONSE_MODE_NOT_PERMITTED |
mode outside the effective set |
ESRA_FILTER_NOT_PERMITTED |
filter field or operator not exposed |
ESRA_CAPABILITY_UNAVAILABLE |
exposure unpublished or configuration invalid at invocation |
ESRA_NO_SOURCES_AVAILABLE |
effective source set empty after resolution |
ESRA_MANDATORY_SOURCE_FAILED |
a mandatory source failed and policy is fail-closed |
ESRA_TIMEOUT |
total budget exhausted before any result |
ESRA_CANCELLED |
caller or host cancelled |
ESRA_DOCUMENT_NOT_FOUND |
fetch: ref invalid, expired, replayed, or no longer authorized |
ESRA_FETCH_NOT_PERMITTED |
fetch called on an exposure with fetch disabled |
ESRA_FETCH_QUOTA_EXCEEDED |
caller exceeded the hourly fetch character quota |
ESRA_INTERNAL |
anything unclassified |
Warnings — the invocation succeeds with status: degraded:
| Code | Meaning |
|---|---|
ESRA_SOURCE_UNAVAILABLE |
an optional source could not be reached |
ESRA_SOURCE_TIMEOUT |
a source exceeded its command timeout |
ESRA_SOURCE_PARTIAL |
a source returned truncated results |
ESRA_PLAN_REJECTED |
one or more generated queries failed validation |
ESRA_SELECTION_FALLBACK |
source selection fell back to mandatory-only |
ESRA_PLANNING_FALLBACK |
planning fell back to the default template |
ESRA_RERANK_FALLBACK |
reranking failed; deterministic ordering used |
ESRA_ANSWER_UNAVAILABLE |
answer generation failed or is not permitted |
ESRA_RESULTS_TRUNCATED |
the requested top-K exceeded the effective cap |
ESRA_BUDGET_EXCEEDED |
a stage hit its deadline and degraded |
ESRA_CONFIGURATION_DRIFT |
a bound source disappeared since publication |
Diagnostics
Try a query
On an exposure's Try & trace tab, an administrator runs one real retrieval and sees every stage of it. It is not a simulation: the same orchestrator, the same configuration and the same providers a caller reaches, running as one of the exposure's own client applications — so narrowing, audience and tool policy all apply. A preview that approximated the pipeline would be most convincing exactly where it was wrong, and an operator whose preview returns nothing has learned the truth about their configuration rather than about a bypass.
What it changes is the audit disposition, not the execution. The stage records are captured and
returned instead of being written, and the preview writes one esra.preview operator-action record
carrying the prompt, the status, the result count and the duration — never the results. An administrator
testing a query must never appear in the trail as a caller having retrieved data: that trail answers "who
read this record", and it is the one question it has to answer truthfully.
It requires ESRA_DATA_MANAGE, not the exposure role. Previewing returns real records from a real
source, so the authority it needs is the authority to see that data — not the authority to decide who
else may.
The response shows what a caller never sees: the resolved effective scope with narrowing notes, the generated plans, each query's rendered parameter metadata, per-source timings and result counts, all four per-result scores — provider, normalized, semantic and final — the deduplication merge, and every fallback taken.
An exposure that is not published, or has no client application bound, reports why it cannot run rather than reporting no results. The two need different responses from the operator: one is a configuration change, the other is a fact about the corpus.
Invocation traces
The same tab lists recent invocations and opens any one of them as a stage trace. This is a read model over the governance audit records ESRA already writes — there is no second store, because a second copy of the same facts could disagree with the audit trail, and the trail is the one that has to be true. Preview records are excluded from the listing, for the same reason they are not invocations.
The timestamp in the When column expands that run's stages inside its own row — stage, outcome, duration and detail — and clicking it again collapses them. The stages belong to the run above them, so they are a child row rather than a panel under the table: with several runs listed, a trace rendered at the bottom said nothing about which one produced it. Both the listing and the trace read a tenant's audit trail, so both take the exposure under inspection: it names the tenant for a platform administrator, whose session pins none. When either cannot be read, the panel says why — an empty listing means the exposure has never been called, and it must not be shown for a request that simply failed.
The trace is what separates four situations that look identical from an empty result set: the corpus has no answer, the selector skipped every source, the plan was rejected, or a provider query timed out. Each calls for a different response, and the results alone tell them apart not at all.
Outcomes are shown as OK, Partial, Failed and Not run, each explaining what it means for the
answer. Partial is the one worth knowing: the step produced a result but not its best one, and its
commonest cause is that no model is configured for the tenant, so the deterministic path ran — a setting
rather than a fault. The audit trail keeps the pipeline's own words (Success, Degraded, Failed,
Skipped); the screens do not.
Each row carries an outcome — succeeded, degraded or failed, rolled up from the stages, worst wins — and a failed one names the stage that broke, the class of failure, and the reason that stage recorded. It names the first failure rather than the last: a stage that fails usually makes the ones after it fail too, and what an operator needs is the cause. Degraded is reported as degraded rather than as success, which matters most when it is a model stage that quietly fell back to the deterministic path.
Callers get status and warnings. Everything else is administrator-only, deliberately.
What each step records
With Capture request and response payloads on for the exposure, the call flow carries the caller's tool arguments — the prompt among them — and the response they received, on the MCP Call step. The response is captured after governance: masked fields are masked and restricted fields are absent, exactly as the caller saw them.
The retrieval steps carry their own detail whether or not capture is on, because none of it is caller content: which sources were in effective scope, which of them were selected and which were passed over, how many queries were planned and how many rejected, and per provider query the source, the entity, the number of records and whether the result was truncated.
The terms a query searched for follow the capture setting, since they are the caller's own words. They are recorded as ESRA planned them — what had to match, what could match, exact phrases, and what was excluded — rather than as the statement that ran. Excluded terms are worth reading first: they are the usual reason a record somebody expected is missing. The generated SQL is never recorded, and neither is an entity's underlying table: the trail carries no database structure.
The wider call flow
Governance → Call Flow shows an execution end to end, and a retrieval call now appears there in full: the governance check, then every ESRA stage — effective scope, source selection, planning, each provider query, aggregation, reranking, the answer — each as its own node with its own timing, followed by the response. Repeated stages are repeated nodes, because a call that fanned out to two sources and had one time out is not the same call as one that queried once.
Configurability
Nothing is a hard-coded constant. Every numeric and behavioural default is a configured value at three narrowing levels:
platform ceiling (appsettings, operator-controlled — the maximum any tenant may set)
↓
tenant default (Portal ESRA settings)
↓
per-entity override (source / capability / exposure / store)
A value set above the level above it is a validation error, never a silent clamp — consistent with the narrowing-only rule for exposures.
