Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bcbd4a4
feat(frontend): add project-scoped Query Registry
ardaerzin Jun 15, 2026
1500e8c
fix(frontend): show annotations in query matching-traces preview
ardaerzin Jun 15, 2026
ad43f9d
fix(frontend): disable inline filter Apply button when draft is uncha…
ardaerzin Jun 15, 2026
b045f8e
fix(frontend): make inline filter Apply gating compare through a stab…
ardaerzin Jun 15, 2026
1cd534f
feat(frontend): add query molecule with semantic draft diff
ardaerzin Jun 15, 2026
10611f1
feat(frontend): add query version-history expand
ardaerzin Jun 15, 2026
e4f0f4a
fix(frontend): fix query version-history expand interactions
ardaerzin Jun 15, 2026
85e0e69
feat(frontend): query registry parent row shows head version (mirrors…
ardaerzin Jun 15, 2026
9d1a142
feat(frontend): commit query edits through a commit-message modal
ardaerzin Jun 16, 2026
84ed2f9
feat(frontend): query edits use the shared EntityCommitModal
ardaerzin Jun 16, 2026
a309f1d
fix(frontend): refresh query version history after a commit
ardaerzin Jun 16, 2026
9af6410
feat(frontend): add commit-message column to query registry
ardaerzin Jun 16, 2026
3eb546e
feat(frontend): filter v0 + add 'Last modified' tag in query registry
ardaerzin Jun 16, 2026
e106146
feat(frontend): allow archiving individual query revisions
ardaerzin Jun 16, 2026
9b384af
feat(frontend): show + restore archived query revisions inline
ardaerzin Jun 16, 2026
c6bcf53
feat(frontend): move archived query revisions into the Archived tab
ardaerzin Jun 16, 2026
0c954f4
test(frontend): add live-API integration tests for the query entity
ardaerzin Jun 16, 2026
06de86a
perf(frontend): lazily resolve sidebar evaluator switcher list
ardaerzin Jun 16, 2026
35f5945
perf(frontend): gate evaluator revision fan-out behind a lazy enrichm…
ardaerzin Jun 16, 2026
62683c8
perf(frontend): defer evaluator revision fan-out from picker + annota…
ardaerzin Jun 16, 2026
1f0850f
perf(frontend): defer evaluator template catalog fetch on app playgro…
ardaerzin Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,26 @@
(Eng Review Decisions → run-creation orchestration).
- **Depends on / blocked by:** Backend team; relates to the FE evaluations migration
landing first (FE rollback is the interim state).

## Query Registry — fast-follows

### Backend query-usage endpoint (enumerate referencing live evals)
- **What:** Add `POST /queries/revisions/{id}/usage` (or `/queries/{id}/usage`) returning the evaluation-run ids that reference a given query revision.
- **Why:** v1 ships a generic "this query may be in use by a live evaluation" confirm before archive, because there is no reverse-lookup today. This endpoint lets the manage drawer name the specific live evals before archiving — real safety instead of a generic warning.
- **Pros:** Turns the safe-archive UX from 7/10 (generic) to 10/10 (enumerated); reuses data that already exists.
- **Cons:** Backend work (new router/service/DAO); a reverse scan of eval-run references.
- **Context:** The reference data exists, flattened, in the evaluations domain under `QUERY_REFERENCE_KEY = "query_revision"` (`api/oss/src/dbs/postgres/evaluations/utils.py`). Eval runs store `data.steps[].references["query_revision"]`. There is currently NO reverse-lookup endpoint and archive does not block in-use queries (`api/oss/src/core/queries/service.py:844` `archive_query_revision` has no reference check). Verified during the eng review of branch `claude/intelligent-bassi-ca4cc0`.
- **Depends on / blocked by:** None. Independent of the FE registry; the FE swaps the generic confirm for enumeration when this lands.

### (RESOLVED) Revision-history expand — no backend change needed
- **What:** The Query Registry's version-history expand is implemented. Each query
(artifact) row expands to its earlier revisions, lazy-loaded on first expand.
- **Resolution:** Revisions are queried by the **artifact ref** (`query_refs: [{id: queryId}]`),
not the variant ref — `QueryRevisionQueryRequest` accepts `query_refs`, and the
service maps `artifact_refs=query_refs`. Simple queries are single-variant, so this
returns the full version history. The earlier assumption that the list must return
`variant_id` was wrong; the list already returns the artifact `id` (= queryId), which
is all the expand needs.
- **Nice-to-have (not blocking):** the simple-queries list could still surface
`revision_id` so the head row shows its version badge without a fetch, but the expand
works without it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ArchivedQueriesPage from "@agenta/oss/src/pages/w/[workspace_id]/p/[project_id]/queries/archived"

export default ArchivedQueriesPage
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import QueriesPage from "@agenta/oss/src/pages/w/[workspace_id]/p/[project_id]/queries/index"

export default QueriesPage
2,010 changes: 993 additions & 1,017 deletions web/oss/src/components/Filters/Filters.tsx

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions web/oss/src/components/Filters/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export interface Props {
onApplyFilter: (filters: Filter[]) => void
onClearFilter: (filters: Filter[]) => void
buttonProps?: ButtonProps
/**
* Render the editor body inline (no popover/trigger button) — for surfaces
* where the filter IS the primary content, e.g. the Query Registry drawer.
*/
inline?: boolean
/**
* Optional callback to derive a *display-only* view of the local filter
* state. Called whenever the user changes a row in the dialog. The dialog
Expand Down
3 changes: 3 additions & 0 deletions web/oss/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const layoutRouteFlagsAtom = atom<LayoutRouteFlags>((get) => {
const isAnnotations = pathname.includes("/annotations")
const isRegistry = pathname.includes("/variants")
const isObservability = pathname.includes("/observability") || pathname.includes("/traces")
// The Query Registry hosts a full-height InfiniteVirtualTable, like Observability.
const isQueries = pathname.includes("/queries")
// The Audit Log settings tab hosts a full-height InfiniteVirtualTable.
// Scoped to the `tab` query param so other settings tabs keep the default
// (content-flow) layout.
Expand All @@ -78,6 +80,7 @@ const layoutRouteFlagsAtom = atom<LayoutRouteFlags>((get) => {
isAnnotations ||
isRegistry ||
isObservability ||
isQueries ||
isAuditLog,
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {useCallback, useMemo, useState} from "react"

import type {PlaygroundNode} from "@agenta/entities/runnable"
import {
activateEvaluatorEnrichmentAtom,
deriveWorkflowTypeFromRevision,
getWorkflowTypeColor,
parseWorkflowKeyFromUri,
Expand Down Expand Up @@ -262,10 +263,21 @@ const PlaygroundHeader: React.FC<PlaygroundHeaderProps> = ({className, ...divPro
// labels, and workflow metadata ("N versions · date") for the picker rows.
// splitTypeTag renders the type tag in the row's suffix slot (vertically
// centered) instead of trailing the name.
//
// `lazy`: the adapter + the `evaluatorWorkflowMetaMapAtom` read above sit
// behind the shared enrichment gate, so they resolve no per-evaluator
// revisions until the user reaches for this "Add evaluators" picker
// (`handleActivateEvaluatorPicker`, on pointer-enter/focus). Keeps a plain
// playground load from firing the batched revision fan-out.
const evaluatorWorkflowAdapter = useEvaluatorOnlyAdapter(renderWorkflowRevisionLabel, {
showWorkflowMeta: true,
splitTypeTag: true,
lazy: true,
})
const activateEvaluatorEnrichment = useSetAtom(activateEvaluatorEnrichmentAtom)
const handleActivateEvaluatorPicker = useCallback(() => {
activateEvaluatorEnrichment()
}, [activateEvaluatorEnrichment])

// Controlled state for EvaluatorTemplateDropdown
const [templateDropdownOpen, setTemplateDropdownOpen] = useState(false)
Expand Down Expand Up @@ -505,7 +517,11 @@ const PlaygroundHeader: React.FC<PlaygroundHeaderProps> = ({className, ...divPro
* playground doesn't make sense (would evaluate itself). */}
{currentWorkflowCtx.workflowKind !== "evaluator" && <RunEvaluationButton />}
<Divider orientation="vertical" className="!mx-0 h-5" />
<span className="relative inline-flex">
<span
className="relative inline-flex"
onPointerEnter={handleActivateEvaluatorPicker}
onFocus={handleActivateEvaluatorPicker}
>
<Tooltip title="Add evaluators to automatically score outputs in the playground.">
<span>
<EntityPicker<WorkflowRevisionSelectionResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ const PlaygroundVariantConfigHeader = ({
(entityData as {flags?: {is_evaluator?: boolean} | null} | null)?.flags?.is_evaluator,
)

// Browse adapters: evaluator-only or app-only (non-evaluator, non-human)
const evaluatorOnlyAdapter = useEnrichedEvaluatorOnlyAdapter()
// Browse adapters: evaluator-only or app-only (non-evaluator, non-human).
// The evaluator adapter is only USED when this is an evaluator entity (see
// `browseAdapter` below); on an app playground it's built but unused, so keep
// its evaluator-enrichment fan-out dormant (`lazy`) there. For evaluator
// entities it's needed, so activate eagerly.
const evaluatorOnlyAdapter = useEnrichedEvaluatorOnlyAdapter(undefined, {
lazy: !isEvaluatorEntity,
})
const appOnlyAdapter = useMemo(
() =>
createWorkflowRevisionAdapter({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {memo, useCallback, useMemo, useState} from "react"

import {testcaseMolecule} from "@agenta/entities/testcase"
import {parseEvaluatorKeyFromUri, workflowMolecule} from "@agenta/entities/workflow"
import {evaluatorTemplatesDataAtom, evaluatorPresetsAtomFamily} from "@agenta/entities/workflow"
import {
evaluatorTemplatesDataAtom,
evaluatorPresetsAtomFamily,
type EvaluatorCatalogTemplate,
} from "@agenta/entities/workflow"
import {
PlaygroundConfigSection,
LoadEvaluatorPresetModal,
Expand All @@ -15,7 +19,7 @@ import {
import {hasPendingHydrationAtomFamily} from "@agenta/playground"
import {Select} from "antd"
import clsx from "clsx"
import {useAtomValue, useSetAtom} from "jotai"
import {atom, useAtomValue, useSetAtom} from "jotai"
import dynamic from "next/dynamic"

import {extractJsonPaths, safeParseJson} from "@/oss/lib/helpers/extractJsonPaths"
Expand All @@ -27,6 +31,9 @@ import type {VariantConfigComponentProps} from "./types"

const RefinePromptModal = dynamic(() => import("../Modals/RefinePromptModal"), {ssr: false})

// Stable empty catalog read for non-evaluator workflows (avoids the templates fetch).
const EMPTY_TEMPLATES_DATA_ATOM = atom<EvaluatorCatalogTemplate[]>([])

/**
* PlaygroundVariantConfig manages the configuration interface for a single variant.
* All entity types (including ephemeral workflows from traces) go through PlaygroundConfigSection.
Expand Down Expand Up @@ -64,16 +71,21 @@ const PlaygroundVariantConfig: React.FC<
const runnableData = useAtomValue(workflowMolecule.selectors.data(variantId))
const dispatchUpdate = useSetAtom(workflowMolecule.actions.updateConfiguration)

// Read evaluator template definitions (workflow-based)
const evaluatorDefinitions = useAtomValue(evaluatorTemplatesDataAtom)

// Determine if this is an evaluator workflow
const evaluatorKey = useMemo(() => {
const uri = runnableData?.data?.uri as string | undefined
if (!uri || !uri.startsWith("agenta:builtin:")) return null
return parseEvaluatorKeyFromUri(uri)
}, [runnableData?.data?.uri])

// Read the evaluator template catalog only for evaluator workflows — apps
// never use it, and an unconditional read fetches GET /evaluators/catalog/
// templates on every playground load (mirrors the workflow molecule, which
// also reads the catalog only once an evaluatorKey is resolved).
const evaluatorDefinitions = useAtomValue(
evaluatorKey ? evaluatorTemplatesDataAtom : EMPTY_TEMPLATES_DATA_ATOM,
)

const evaluatorDef = useMemo(() => {
if (!evaluatorKey) return null
return evaluatorDefinitions.find((e) => e.key === evaluatorKey) ?? null
Expand Down
5 changes: 5 additions & 0 deletions web/oss/src/components/QueryRegistry/ArchivedQueriesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import QueryRegistry from "."

export default function ArchivedQueriesPage() {
return <QueryRegistry mode="archived" />
}
Loading
Loading