Studio Backend
Analytics Suite
The Studio’s Analytics page and the analytics tools of the MCP server read the conversation records the
engine stores in MongoDB. AnalyticsRestController exposes one endpoint per card, all computed in
AnalyticalService with aggregations in SessionAnalyticsRepository and
EvaluationResultAnalyticsRepository. Every endpoint requires the Viewer role on the project.
Filters
The card endpoints take the same body (AnalyticsQuery):
| Field | Meaning |
|---|---|
|
|
|
Restrict to sessions held with an agent revision in one state, for example only |
|
The window: the last |
|
Only the conversations of one end user (the Filter by user control). |
Changes ("+12 %") compare the window with the window of the same length right before it.
Who Is Counted
Simulated users are not traffic of the project’s agents: a selection that does not name its agents —
ALL, or EXCLUDE — leaves out every TEST agent of the project (AnalyticsAgentScope). An INCLUDE
selection is read as given, so a simulated user can still be inspected on purpose. Sessions in test mode
— the tested agent’s side of a test-suite run — are always excluded, and so are the results of the
evaluations run on them.
The conversations of Promethist staff accounts — usernames ending in @promethist.ai, the people who
build, test and demo an account’s agents — count only for a viewer who is staff too. For anyone else,
every read leaves them out, also when the user filter names one of them (AnalyticsPopulation, which
caches the list of staff accounts for five minutes). This is the same exception staff have from the
account privacy settings (Account Settings That Apply).
Cards and Endpoints
All paths are under /api/v1/project/{projectId}.
| Endpoint | Computes |
|---|---|
|
Sessions in the window and their change, sessions bucketed by turn count, the lifetime total. |
|
Total and average conversation length (seconds) with change, average turn count, and the same figures over the project’s lifetime. |
|
Unique end users in the window with change, lifetime unique users, and a trend series whose bucket width follows the window. |
|
A histogram: how many users had one, two, three… conversations in the window. |
|
Sessions per agent, every revision of an agent counted together, most used first. |
|
The end users the analytics count (Who Is Counted), paged — the options of the user filter. |
|
Per interactive-content element: how often it was executed, with parameter and action counts (read
from each session’s |
|
Aggregates of the evaluation results: featured metrics, boolean shares, number statistics, text samples and category histograms, plus per-element user statistics. |
|
Featured evaluation outcomes per user. |
|
The report as a file, see Exports. |
How the Figures Are Computed
- Sessions
-
Sessions come from the
sessioncollection, filtered by the project’s ref, the window, the agent selection, the state of the agent revision the session ran on and the user, without the sessions left out under Who Is Counted. Session and user counts skip sessions in which no turn completed; the engine’sturnCountruns one ahead of the stored turns, whichTurnMathinutil/Functions.ktaccounts for. - Conversation length
-
A conversation’s length is its connected time: the sum of the
durationof the session’sRealtimerecords in the engine’sedrcollection — one per realtime connection, written when the connection closes, and the records billing charges for. A user who left and came back days later adds only the time actually connected. A session without such a record, usually one still connected, has no length yet: it is left out of the total and of the average’s denominator instead of counting as zero, while its turns still count towards the average turn count.EdrIndexMigrationensures a partial{sessionId, duration}index onedrthat answers the lookup from the index alone; the cost is one lookup per session. Exports report the same per-session figure, empty while none is recorded. - Session end
-
The end shown in exports is the
createdtime of the session’s newest stored turn plus a fixed allowance for that turn — a timestamp only, which is why the length is not end minus start. The newest turn is looked up bysessionId, not by the session’slastTurnId, which runs one turn ahead of what is stored, because the engine opens the next turn as soon as a turn’s output completes.TurnIndexMigrationensures the{sessionId, created}index onturnthat makes the lookup an index seek. - Lifetime figures
-
The lifetime variants cover every session the project ever had, so they are computed once per filter and cached for ten minutes. A request waits at most five seconds for them and otherwise answers without them (or with the previous value) while the computation finishes in the background for the next request.
- Evaluations
-
Only evaluations that run at the end of a conversation and are admin-facing reach the analytics. The featured-outcome card reads, for the users with a featured result in the window, their results over their whole history — someone who passed in June and came back this week counts as passed. Agent evaluations (
testMode) are left out, and so are the results of the sessions and users left out under Who Is Counted.EvaluationResultIndexMigrationensures the indexes these reads use. - A conversation’s evaluation results
-
The Conversations panel shows the selected conversation’s results in place of its transcript (
GET /api/v1/project/{projectId}/sessions/{sessionId}/evaluationsinSessionController, behind the transcript’s gates;SessionEvaluationService): the latest conversation-end run of each of the evaluations above, its elements in the evaluation’s order and worded by their definitions. A result is matched to the evaluation’s current element by id, then by ref, because saving an evaluation recreates its elements. A session or user left out under Who Is Counted gets no results. While the engine has yet to mark the session evaluated (evaluationResultsStatestillNOT_READY, the session changed within the last 15 minutes), the answer ispending, and the Studio asks again every ten seconds while it is.
Account Settings That Apply
| Setting | Effect on the analytics |
|---|---|
|
Every evaluation aggregate that fewer than two users stand behind is withheld and counted instead; the
Studio shows how many were withheld. Featured outcomes of evaluations too few users tried are left out
as well, and a conversation’s own results, always one user’s, are withheld entirely (the answer is
only |
|
When off, exports contain no evaluation results. |
|
When off, exports contain no transcripts, and the conversation list, transcripts and a conversation’s
evaluation results are not readable ( |
Staff are not restricted by these settings (Account Privacy Settings).
Exports
POST /api/v1/project/{projectId}/analytics/export (AnalyticsExportController,
AnalyticsExportService) produces the report for a mandatory from–to range:
| Format | Content |
|---|---|
|
Sheets Summary (the filters and the conversation, user and length figures), Sessions (one row per session: id, user, agent, start and end, turns, duration and, when allowed, the transcript) and Evaluations (the per-session evaluation results, when allowed). |
|
A zip archive with one flat CSV per table, ready for pandas, Excel or BI tools. |
|
The same report as one JSON document. |
A caller can leave transcripts out even where the account would allow them
(includeTranscript: false). Text cells are protected against spreadsheet formula injection.
The report is streamed end to end, so neither the rows nor the file are ever held in memory whole:
-
Everything that decides the export is settled before the first byte: the range and the format are checked (
400), a range with more than 100,000 sessions is refused (413, with a request to narrow it), and the account settings decide whether transcripts and evaluation results are included. A refusal therefore still arrives as an error response. -
The session rows and evaluation results are then read in small batches while the file is written. The workbook is Apache POI’s streaming variant, which keeps only the last 100 rows of a sheet in memory and moves the rest to temporary files.
-
An xlsx export with more evaluation results than one sheet holds (1,048,575 rows below the header) is refused with
413as well, pointing to CSV or JSON. That is only known once the rows are read, but a workbook sends nothing before it is complete, so this refusal also arrives as an error response. -
The response goes out in 32 KB chunks as they are produced, with no
Content-Length. A failure after the first chunk can only cut the download short. -
An Excel cell holds at most 32,767 characters. A longer text — a very long transcript — is cut to fit and ends with a marker that gives its full length; the CSV and JSON exports carry it whole.
The Studio saves the downloaded file through a temporary object URL that stays valid for 60 seconds
(lib/trigger-browser-download.ts, shared with the test-suite export), because revoking it right after
the click can cancel a large download.
The MCP tool export_analytics builds the same report. JSON is returned inline; xlsx and csv files are
stored as they are rendered — at most 200 MB per file — and returned as a download link
(Capability URLs).
Conversation Data for the MCP Server
Beyond the aggregated analytics, the MCP server gives Claude read access to the raw records
(McpConversationDataTools, ConversationDataService):
-
list_sessionsandget_session_transcriptgo throughSessionController, with its role check and the full-conversation-data setting. Like the Studio’s list,list_sessionsskips test-mode sessions, very short ones and, for a caller who is not staff, those of staff accounts. -
search_turns,search_llm_logs,describe_conversation_dataandexport_conversation_dataread the engine’s Elasticsearch indexes through the index patternspromethist.mcp.data-views.*. The engine writes one index per scope (the project ref) and month,{scope}-{turn|llmlog}-{namespace}-{yyyy.MM}, so a pattern spans all of them. Every query is restricted to the accounts in which the caller holds an account-level role — project-level rows are deliberately not used, because the documents carry only a tenant id — and an account with full conversation data switched off drops out of that set. Documents without a tenant id, written before the field existed, are never returned. -
export_conversation_datastreams up to 100,000 documents (10,000 by default) as NDJSON, or as CSV for turns, into GridFS — at most 200 MB per file — and returns a 24-hour download link. -
fields, onexport_conversation_dataandsearch_llm_logs, is passed to Elasticsearch as_sourceincludes, so a narrowed document never leaves the cluster whole and never counts against the 200 MB cap. Wildcards are allowed;createdand the identifiers are added to whatever is asked for; includes replace the default exclude list rather than combining with it, because Elasticsearch applies excludes last. It is refused for CSV, whose columns are fixed, and alongsideinclude_full, which asks for the opposite. Anllmlogrecord of a relational planner call is around 290 KB, nearly all of it the agent’s configuration repeated per call, so this is the difference between a pilot’s model calls fitting in one export and in hundreds.