diff --git a/packages/app/cypress/support/e2e.ts b/packages/app/cypress/support/e2e.ts
index 0edb08c0..8d03599e 100644
--- a/packages/app/cypress/support/e2e.ts
+++ b/packages/app/cypress/support/e2e.ts
@@ -16,11 +16,14 @@ Cypress.on('window:before:load', (win) => {
});
/**
- * Unlock the shared feature gate for specs that exercise agentic surfaces
- * (the "Agentic Traces" scenario, /datasets, /inference/agentic/[id], and the
- * Datasets nav link). The gate is OFF by default so the PR can ship without
- * publicly exposing agentic features; agentic specs opt in by seeding the same
- * localStorage flag the ↑↑↓↓ konami unlock writes (see use-feature-gate.ts).
+ * Seed the shared feature-gate flag (the same localStorage key the ↑↑↓↓ konami
+ * unlock writes — see use-feature-gate.ts).
+ *
+ * The agentic surfaces (the "Agentic Traces" scenario, /datasets,
+ * /inference/agentic/[id], and the Datasets nav link) are now PUBLIC by default
+ * — they no longer sit behind this gate — so agentic specs no longer need it.
+ * The helper is retained as a harmless no-op for those specs (and still unlocks
+ * the remaining hidden features: the "Hidden" tab dropdown and Measured Energy).
*
* Call from a spec's `cy.visit(..., { onBeforeLoad })`:
* cy.visit('/datasets/x', { onBeforeLoad: unlockAgenticGate });
@@ -30,6 +33,7 @@ export function unlockAgenticGate(win: Window): void {
try {
win.localStorage.setItem('inferencex-feature-gate', '1');
} catch {
- // localStorage unavailable — spec will see the gate locked and likely 404.
+ // localStorage unavailable — only the remaining hidden features stay locked;
+ // agentic surfaces are public regardless.
}
}
diff --git a/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx b/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx
index 91b769bd..f2612c84 100644
--- a/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx
+++ b/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx
@@ -1,7 +1,6 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
-import { AgenticGate } from '@/components/agentic-gate';
import { AgenticPointDetail } from '@/components/inference/agentic-point/agentic-point-detail';
import { isPersistedBenchmarkId } from '@/lib/benchmark-id';
@@ -21,9 +20,5 @@ export default async function AgenticPointDetailPage({
// `/agentic/0`, `/agentic/-1`) can never resolve, so 404 instead of rendering a
// blank detail shell that fires doomed id-keyed fetches.
if (!isPersistedBenchmarkId(numericId)) notFound();
- return (
-
-
-
- );
+ return ;
}
diff --git a/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx b/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx
index 5bc8fea9..8957c8b4 100644
--- a/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx
+++ b/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx
@@ -1,7 +1,6 @@
import { Suspense } from 'react';
import type { Metadata } from 'next';
-import { AgenticGate } from '@/components/agentic-gate';
import { ConversationView } from '@/components/datasets/conversation-view';
import { SITE_URL } from '@semianalysisai/inferencex-constants';
@@ -32,14 +31,12 @@ export default async function ConversationPage({ params }: Props) {
// A second decodeURIComponent here would over-decode (and throw for ids that
// contain a literal '%'). ConversationView re-encodes when it builds the API URL.
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/packages/app/src/app/datasets/[slug]/page.tsx b/packages/app/src/app/datasets/[slug]/page.tsx
index c853a695..f32e3fa6 100644
--- a/packages/app/src/app/datasets/[slug]/page.tsx
+++ b/packages/app/src/app/datasets/[slug]/page.tsx
@@ -1,6 +1,5 @@
import type { Metadata } from 'next';
-import { AgenticGate } from '@/components/agentic-gate';
import { DatasetDetail } from '@/components/datasets/dataset-detail';
import { SITE_URL } from '@semianalysisai/inferencex-constants';
@@ -24,12 +23,10 @@ export async function generateMetadata({ params }: Props): Promise {
export default async function DatasetDetailPage({ params }: Props) {
const { slug } = await params;
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
}
diff --git a/packages/app/src/app/datasets/page.tsx b/packages/app/src/app/datasets/page.tsx
index 711e0dbc..7fe46b93 100644
--- a/packages/app/src/app/datasets/page.tsx
+++ b/packages/app/src/app/datasets/page.tsx
@@ -1,6 +1,5 @@
import type { Metadata } from 'next';
-import { AgenticGate } from '@/components/agentic-gate';
import { Card } from '@/components/ui/card';
import { JsonLd } from '@/components/json-ld';
import { DatasetList } from '@/components/datasets/dataset-list';
@@ -30,14 +29,6 @@ const jsonLd = {
};
export default function DatasetsPage() {
- return (
-
-
-
- );
-}
-
-function DatasetsPageContent() {
return (
diff --git a/packages/app/src/components/GlobalFilterContext.tsx b/packages/app/src/components/GlobalFilterContext.tsx
index 8bd10c71..11a75324 100644
--- a/packages/app/src/components/GlobalFilterContext.tsx
+++ b/packages/app/src/components/GlobalFilterContext.tsx
@@ -36,7 +36,6 @@ import {
import { computeAutoSwitchDecision } from '@/lib/unofficial-run-auto-switch';
import { countCurvesByPrecision, resolveEffectivePrecisions } from '@/lib/default-precisions';
import { resolveEffectiveSequence } from '@/lib/default-sequence';
-import { useFeatureGate } from '@/lib/use-feature-gate';
import type { AvailabilityRow, WorkflowInfoResponse } from '@/lib/api';
const RUNDATE_RE = /^\d{4}-\d{2}-\d{2}$/u;
@@ -162,14 +161,6 @@ export function GlobalFilterProvider({
}) {
const { hasUrlParam, getUrlParam, setUrlParams } = useUrlState();
- // Agentic surfaces are hidden behind the shared konami-code feature gate
- // (default OFF until agentic launches). When locked, agentic sequences are
- // filtered out of `availableSequences` below — the single chokepoint that
- // cascades: no agentic default (resolveEffectiveSequence falls to 8k/1k), no
- // "Agentic Traces" scenario-selector entry, and no agentic x-axis mode /
- // percentile selector (those key off effectiveSequence === AgenticTraces).
- const agenticGateUnlocked = useFeatureGate();
-
// ── Core filter state ─────────────────────────────────────────────────────
const [selectedModel, setSelectedModel] = useState(
() => initialModel ?? Model.DeepSeek_V4_Pro,
@@ -304,26 +295,17 @@ export function GlobalFilterProvider({
}, [unofficialAvailable, selectedModel]);
// Sequences available for the selected model (DB ∪ unofficial run for this model).
- //
- // When the agentic feature gate is locked (default), agentic sequences are
- // dropped from every branch — including the static SEQUENCE_OPTIONS fallback —
- // so no agentic scenario is ever selectable or defaulted. This is the single
- // gate chokepoint for the main inference chart's agentic surfaces.
const availableSequences = useMemo(() => {
- const dropAgentic = (seqs: Sequence[]) =>
- agenticGateUnlocked ? seqs : seqs.filter((s) => s !== Sequence.AgenticTraces);
const unofficialSeqs = unofficialAvailable
.filter((a) => a.model === selectedModel)
.map((a) => a.sequence as Sequence);
if (!availabilityRows) {
- return unofficialSeqs.length > 0
- ? dropAgentic([...new Set(unofficialSeqs)])
- : dropAgentic(SEQUENCE_OPTIONS);
+ return unofficialSeqs.length > 0 ? [...new Set(unofficialSeqs)] : [...SEQUENCE_OPTIONS];
}
const dbSeqs = modelRows.map((r) => rowToSequence(r)).filter((s): s is Sequence => s !== null);
- const merged = dropAgentic([...new Set([...dbSeqs, ...unofficialSeqs])]);
- return merged.length > 0 ? merged : dropAgentic(SEQUENCE_OPTIONS);
- }, [availabilityRows, modelRows, unofficialAvailable, selectedModel, agenticGateUnlocked]);
+ const merged = [...new Set([...dbSeqs, ...unofficialSeqs])];
+ return merged.length > 0 ? merged : [...SEQUENCE_OPTIONS];
+ }, [availabilityRows, modelRows, unofficialAvailable, selectedModel]);
// Whether we actually know the selected model's sequences yet. Availability
// may arrive from the DB (`availabilityRows`) OR from a loaded unofficial run
diff --git a/packages/app/src/components/agentic-gate.tsx b/packages/app/src/components/agentic-gate.tsx
deleted file mode 100644
index 9fa0aa37..00000000
--- a/packages/app/src/components/agentic-gate.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-'use client';
-
-import { notFound } from 'next/navigation';
-import { useEffect, useState } from 'react';
-
-import { FEATURE_GATE_KEY, useFeatureGate } from '@/lib/use-feature-gate';
-
-/**
- * Client gate for the standalone agentic product pages (`/datasets/*`,
- * `/inference/agentic/[id]`). These are server-rendered routes with no nav
- * entry once the header link is hidden, so a direct URL visit is the only way
- * in. When the shared konami-code feature gate (see {@link useFeatureGate}) is
- * locked — the default until agentic launches — we `notFound()` so the route
- * behaves like a clean 404 instead of publicly exposing agentic surfaces.
- *
- * The gate lives in localStorage, which the server can't read, so we resolve it
- * on the client: read the flag synchronously on mount, and until then render
- * nothing (no content flash before a potential 404). QA can unlock at runtime
- * with ↑↑↓↓ (the same mechanism as the Hidden tab dropdown) or by seeding
- * `localStorage['inferencex-feature-gate'] = '1'`, after which these pages
- * render in full.
- */
-export function AgenticGate({ children }: { children: React.ReactNode }) {
- const unlocked = useFeatureGate();
- // Distinguish "haven't read localStorage yet" from "read it, gate is locked":
- // useFeatureGate() returns false on the server and on the very first client
- // render before its mount effect runs, so we must not 404 during that window.
- const [resolved, setResolved] = useState(false);
- useEffect(() => setResolved(true), []);
-
- if (!resolved) return null;
- if (!unlocked) {
- // Belt-and-suspenders: re-read the flag directly in case an unlock event
- // hasn't propagated yet on this first resolved render.
- if (typeof window !== 'undefined' && localStorage.getItem(FEATURE_GATE_KEY) === '1') {
- return <>{children}>;
- }
- notFound();
- }
- return <>{children}>;
-}
diff --git a/packages/app/src/components/header/header.tsx b/packages/app/src/components/header/header.tsx
index 1a12057e..eebef27d 100644
--- a/packages/app/src/components/header/header.tsx
+++ b/packages/app/src/components/header/header.tsx
@@ -9,7 +9,6 @@ import { track } from '@/lib/analytics';
import { ModeToggle } from '@/components/ui/mode-toggle';
import { MinecraftToggles } from '@/components/minecraft/minecraft-toggles';
import { navigateInApp } from '@/lib/client-navigation';
-import { useFeatureGate } from '@/lib/use-feature-gate';
import { cn } from '@/lib/utils';
import { GitHubStars } from './GithubStars';
@@ -52,9 +51,6 @@ const NAV_LINKS = [
label: 'Datasets',
testId: 'nav-link-datasets',
event: 'header_datasets_clicked',
- // Agentic surface — hidden behind the konami-code feature gate (default off)
- // until agentic launches. Same gate as the Hidden tab dropdown.
- gated: true,
},
{ href: '/blog', label: 'Articles', testId: 'nav-link-blog', event: 'header_blog_clicked' },
{ href: '/about', label: 'About', testId: 'nav-link-about', event: 'header_about_clicked' },
@@ -72,13 +68,10 @@ function isActive(pathname: string, href: string): boolean {
export const Header = ({ starCount }: { starCount?: number | null }) => {
const pathname = usePathname() ?? '/';
const router = useRouter();
- const featureGateUnlocked = useFeatureGate();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const menuRef = useRef(null);
- // Hide gated nav links (e.g. Datasets — an agentic surface) unless the shared
- // feature gate is unlocked. Mirrors the tab-nav "Hidden" dropdown gating.
- const navLinks = NAV_LINKS.filter((l) => !('gated' in l && l.gated) || featureGateUnlocked);
+ const navLinks = NAV_LINKS;
// Close menu on route change
useEffect(() => {