EVALUATOR_ENDPOINT on the server.
Note: You define the score dimensions. Your evaluator can return any numeric keys it likes; Observability stores, trends, and displays whatever you send back.
At a glance
- Write a scorer. Stand up a small HTTP service that reads a session transcript and returns scores. Observability ships a working reference you can copy. See Writing an evaluator with the SDK.
- Point Observability at it. Set
EVALUATOR_ENDPOINT(and a sharedEVALUATOR_TOKEN) on the server process. - Watch the scores land. Every completed session is scored automatically; results show up on the session detail page, the sessions grid, and saved dashboards.

How it works
When the Observability SDK emits anagent_end event for a session, the server
schedules an evaluation. It then POSTs the full event transcript to your
evaluator service, which can either:
-
Return the result inline with
{"status":"done", "scores":{...}, "reasoning":{...}, "summary":"..."}. The result is appended to the session’s evaluation timeline.reasoningandsummaryare optional. -
Defer with
{"status":"pending", "job_id":"abc-123"}. Observability then callsGET {EVALUATOR_ENDPOINT}/evaluate/abc-123until your evaluator returns{"status":"done", ...}or{"status":"error", "error":"..."}. The polling cadence is per-job: apendingresponse may includenext_poll_secsto override; otherwise Observability uses thedefault_poll_interval_secsvalue fromGET /config; otherwise the server falls back toEVALUATOR_POLLING_INTERVAL_SECS(default 10s). All values are clamped to [1s, 1h].
agent_end (for example, a crashed agent process)
can also be picked up: the evaluator’s GET /config may return
{"inactivity_timeout_secs": 1800}, and Observability will evaluate any session
that has gone idle for that long. Set the field to null or omit it to
disable this fallback.
The pipeline is fully no-op when EVALUATOR_ENDPOINT is unset.
A session can accumulate multiple terminal evaluations over time: each
agent_end event (and each manual re-eval from the dashboard) appends a
fresh evaluation row. This is the supported way to evaluate a resumed
conversation: a user ends an agent, comes back later, sends more events,
ends the agent again, and a second evaluation runs against the full updated
transcript. The dashboard renders the most-recent evaluation as the
headline and the prior evaluations as a collapsible timeline. While one
evaluation is running for a session, additional agent_end events for that
session are ignored; the next one after the running evaluation completes
will enqueue a fresh evaluation as usual.
The inactivity fallback re-engages on resumed sessions too: if new events
arrive after a previous terminal evaluation and the session then goes idle
past inactivity_timeout_secs, a fresh evaluation is enqueued.
Transient failures (5xx, 429, timeouts, network errors) are retried with
exponential backoff up to EVALUATOR_MAX_ATTEMPTS; 4xx responses are
terminal. Observability is safe to run with multiple horizontally-scaled server
instances; work is partitioned so the same session is never dispatched
twice concurrently.
HTTP contract
Every authenticated route uses bearer token auth. The same value must be configured on both sides:- Observability server: env var
EVALUATOR_TOKEN - Evaluator service: configured the same way (the
agenteye-evaluatorSDK readsEVALUATOR_TOKENby convention)
EVALUATOR_TOKEN is unset, the server sends no Authorization header; the
evaluator may then accept anonymous requests, which is fine for an
internal-only network but discouraged on the public internet.
Routes the evaluator must serve
EvalRequest body sent by the server
Response shapes
Sync (done):reasoning (a per-score justification map) and summary (an overall
one-paragraph narrative) are both optional. Keys in reasoning should
mirror keys in scores; the dashboard renders each entry inline under
its score bar. Older evaluators that return only scores continue to
work unchanged; reasoning and summary simply read as null and
the corresponding UI affordances are omitted.
Async (deferred):
next_poll_secs is optional; if omitted the server falls back to the
evaluator’s default_poll_interval_secs from /config, then to its own
EVALUATOR_POLLING_INTERVAL_SECS env var.
Terminal evaluator-side error:
error for the session.
Writing an evaluator with the SDK
You don’t have to implement the HTTP contract by hand. Theagenteye-evaluator
Python package gives you a typed FastAPI wrapper that handles auth, routing, and
the request/response shapes for you.
Failproof AI Observability also ships a working reference evaluator that
scores helpfulness, tool_efficiency, and factuality from the shape of the
transcript. Copy it as a starting point and swap in your own logic: an LLM
judge, a rule engine, whatever fits your quality bar.
Minimum viable evaluator:
app instance runs under any ASGI server, so uvicorn module:app starts it.
For evaluators that need to defer expensive work, return JobPending
instead and register a @app.job_lookup handler; the Observability server
polls GET /evaluate/{job_id} until you return a terminal status or the
EVALUATOR_MAX_POLL_DURATION_SECS cap (default 1 h) elapses.
The full API reference, async pattern, and event schema are documented in the
agenteye-evaluator SDK’s README.
Running your evaluator
The evaluator is your service — Failproof AI Observability does not ship a default evaluator, so you build and run it wherever you run your own services. It runs under any ASGI server (for exampleuvicorn my_evaluator:app); serve
the /health, /config, and /evaluate routes from the
HTTP contract, then point the server at it (see
Configuring the server).
Once the evaluator is reachable, GET /health returns {"status":"ok"}. After
an agent runs end-to-end, GET /evaluations on the server returns a row with
status: "done" and the scores your evaluator produced.
Configuring the server
Set on the server process:
To turn on automatic scoring, set both
EVALUATOR_ENDPOINT and
EVALUATOR_TOKEN on the server, then restart it to pick up the change. With
EVALUATOR_ENDPOINT unset the pipeline stays a no-op.
The tuning knobs above are optional; set the corresponding environment
variables on the server only if you need to override the defaults.
API reference
Filtering by score range: score_filters
GET /evaluations accepts an optional score_filters parameter that
narrows results by numeric values inside the scores object. The
parameter is a comma-separated list of key:min..max entries; either
bound may be omitted. Multiple entries combine with logical AND. Rows
where the named key is absent or non-numeric are excluded. A request may
carry at most 20 filter entries; exceeding that returns HTTP 400.
Examples:
/evaluations response object has these fields:
Permissions
The bootstrap admin (
ADMIN_KEY, ADMIN_EMAIL) automatically receives these.
Viewing results
/sessions/<id>: events timeline + a right rail showing the session’s scores and any error from the dispatch attempt. If your key hasevaluations:trigger, a re-evaluate button appears next to the export button, useful for sessions that never emittedagent_end, or for refreshing scores after deploying a new evaluator. The dashboard polls for the new result and updates the right rail when it lands./sessions: filterable session grid; the score column shows each session’s evaluation status and scores at a glance./dashboards: saved eval-health views (see Dashboards below).

Dashboards
The Dashboards page (/dashboards) lets you save a combination of evaluation
filters as a named, reusable view and watch how that slice of evaluations is
doing at a glance. Dashboards are shared across your whole organization;
everyone with dashboards:read sees the same set.
Each dashboard pins:
- Filters: the same controls as the sessions page: environment, status,
agent, a rolling time window, and score-range filters (
key:min..max). - A display configuration: which score keys to feature, the green/amber/red health thresholds, which panels to show, and whether to collapse to the latest evaluation per session.
GET /evaluations/aggregate), so
the numbers are exact rather than sampled.

dashboards:read and evaluations:read;
creating and editing needs dashboards:write; deleting needs dashboards:delete.
The bootstrap admin receives all of these automatically.
Troubleshooting
Sessions exist but no evaluations are created. ConfirmEVALUATOR_ENDPOINT
is set on the server process, that the server and evaluator share the same
EVALUATOR_TOKEN value, and that the evaluator’s /health endpoint is
reachable from the server. With EVALUATOR_ENDPOINT unset the pipeline is a
no-op.
In-flight evaluations pile up. Query GET /evaluation-jobs to see the
in-flight queue. Inspect attempt_count, next_attempt_at, and last_error
on each row. Common causes: evaluator service unreachable or returning 5xx
(retried with backoff), wrong EVALUATOR_TOKEN (401 is terminal), or an
async evaluator that returns pending indefinitely (see below).
Sessions completed but no terminal evaluation. Query
GET /evaluation-jobs?status=polling; the result may still be in flight.
If a job is stuck in pending, the server is having trouble reaching the
evaluator; check that the evaluator is up and that EVALUATOR_TOKEN matches.
HTTP 401 from evaluator: invalid bearer token. The EVALUATOR_TOKEN
on the server does not match the value the evaluator service is configured
with. They must be identical.
Async evaluator returns pending forever. The server polls
GET /evaluate/{job_id} until the evaluator returns done or error, or
until EVALUATOR_MAX_POLL_DURATION_SECS (default 1 h) elapses. After the cap
the evaluation is recorded as timeout and removed from the in-flight queue.
Raise EVALUATOR_MAX_POLL_DURATION_SECS if your evaluator legitimately needs
longer than the default.
Next steps
- Evaluator agent skill: have a coding agent design your dimensions against real sessions and build this service for you.
- Python SDK: emit the
agent_endevents that trigger scoring. - API keys: the
evaluations:readandevaluations:triggerpermissions. - Audits: Observability’s other automated quality feature, for policy-based review.

