Judgment Labs Logo

Queries and terminals

TypeScript JQL roots, time bounds, select terminals, and result row shapes.

judgeval-js JQL source on GitHub

Root builders

traces(options?: { filter?: FilterInput }): QueryBuilder
spans(options?: { filter?: FilterInput }): QueryBuilder
sessions(options?: { filter?: FilterInput }): QueryBuilder
offline_traces(options?: { filter?: FilterInput }): QueryBuilder
offline_spans(options?: { filter?: FilterInput }): QueryBuilder
SourceGrainCanonical fields
tracesOne row per tracetrace_id, span_id, span_name, name, input, output, duration, status, timestamp, session, customer, error
spansOne row per spanCommon fields plus cost, model, parent_span_id
sessionsOne row per sessionCommon fields plus error
offline_tracesOne row per offline traceLive trace fields except session; backed by the offline trace store
offline_spansOne row per offline spanLive span fields except session; backed by the offline trace store

Offline roots read traces captured by an OfflineTracer. They support the same direct terminals as their live twins, but do not expose sessions, judge scores, or cross-grain relations.

Field catalogs can evolve. Use client.discover("fields", { source: "spans" }) before generating dynamic query UIs or relying on project-specific attributes.

Builder methods

.where(filter: FilterInput): QueryBuilder
.last(window: string): QueryBuilder
.since(since: string): QueryBuilder
.between(start: string, end: string): QueryBuilder
.rows(options?: { fields?: string[]; limit?: number }): QueryBuilder
.ids(): QueryBuilder
.count(by?: string): QueryBuilder
.recent(n: number): QueryBuilder
.top(n: number, by: string): QueryBuilder
.ranked(options?: { within?: string; by?: string; pick?: number | [number, number] }): QueryBuilder
.agg(options: { func; field; q? }): QueryBuilder
.agg(func, field, q?): QueryBuilder
.trend(options?: { metric?: "count" | "rate"; bucket?: string }): QueryBuilder
.pipe(): PipelineBuilder
.toJSON(): SourceQuery

Builders are immutable: every method returns a new builder. Calling .where() multiple times combines the root filters with canonical all (AND). Calling .last(), .since(), or .between() replaces any earlier time bound.

Serialize without executing with query.toJSON().

Time object

The JSON time object must contain exactly one key:

BuilderJSONInput
.last(window){"last":"7d"}Relative duration string
.since(since){"since":"2026-07-01"}Date/time string accepted by the server
.between(start, end){"between":["2026-07-01","2026-07-08"]}Exactly two date/time strings

Omitting time searches all available history unless a deployment-level policy adds a default window.

Select terminals

A source query can contain at most one select. In TypeScript, a second select silently replaces the first — the builder does not throw. The server also rejects a query containing both select and pipe.

TerminalInputs and defaultsResult rows
rowsfields?: string[], limit?: numberRequested fields, or the source's default row projection
idsNoneDistinct ID columns for the grain: trace roots return trace_id; span roots return both trace_id and span_id; sessions returns session_id
countby?: stringNo by: {"count": number}; with by: {"key": value, "count": number}
recentn: intSource rows ordered by newest timestamp
topn: int, by: stringSource rows ordered by the numeric field descending
rankedwithin?: string, by: "timestamp", pick: 1Trace/span grains, including offline. Positional rows plus _rn; pick is a nonzero rank or two-item range 1 <= a <= b
aggfunc, field, q?Trace/span grains, including offline. Exactly one row: {"value": number | null}
trendmetric: "count", bucket: "1d"Count: {"bucket": datetime, "n": number}; rate: adds total and rate

Aggregate rules

func is one of avg, sum, min, max, quantile, or count_distinct. field is always required. q is required only for quantile, rejected for every other function, and must satisfy 0 < q < 1.

Trend rules

metric is count or rate. bucket is a duration string such as 1d or 1w. Rate trends require a filter that defines the numerator.