diff --git a/docs/usage.md b/docs/usage.md
index f7cc42fcc..fda43de5c 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -80,6 +80,52 @@ navigating to explicit range URLs also pins the range while the
bare page URL returns to rolling mode. The `All` preset always
pins to `(earliest_session, today)`.
+### Model Filter
+
+The dashboard toolbar includes a **Model** dropdown that scopes
+every panel to one or more AI models. By default the button reads
+**Model: All** and nothing is filtered.
+
+
+
+Open the dropdown for a searchable list of the models found in
+your sessions, then click models to include them. The button then
+shows the chosen model — for example **Model: gpt-4o** — or
+**Model: 3 selected** once several are active. Click the
+**All models** row at the top of the list to clear the filter and
+return to every model. Selected models also appear as removable
+chips beneath the toolbar.
+
+While a model filter is active, every dashboard panel reflects
+only the selected model(s): the summary cards, activity chart,
+heatmap, hour-of-week grid, projects, session shape, velocity,
+tools, skills, top sessions, and the Session Health rollup.
+
+Model filtering is **message-grain**, unlike the session-grain
+project and agent filters, because a single session can switch
+models across turns. A session is included when it has at least
+one message from a selected model, and most panels count only the
+matching messages. The user turn paired with a matching assistant
+turn is kept alongside it — even though a user message carries no
+model of its own — so prompts and their responses stay aligned in
+the counts and in the top-session evidence.
+
+**Session Health** is the exception. It is scoped to whole
+sessions that used the selected model, but its health scores,
+outcomes, tool-failure rates, and compaction counts stay
+whole-session aggregates — they are not recomputed from only that
+model's messages.
+
+!!! note "Dashboard-only scope"
+ The model filter applies only to the analytics dashboard. The
+ [Session Insights](/insights/) page and the session list are
+ not scoped by it, so a model selected here does not silently
+ narrow those views. The [Usage](/token-usage/) page keeps its
+ own separate model filter.
+
### Activity Heatmap
A GitHub-style contribution graph showing daily activity. Toggle
diff --git a/frontend/src/lib/api/generated/models/DbAnalyticsSummary.ts b/frontend/src/lib/api/generated/models/DbAnalyticsSummary.ts
index c91bb7554..7a12ad502 100644
--- a/frontend/src/lib/api/generated/models/DbAnalyticsSummary.ts
+++ b/frontend/src/lib/api/generated/models/DbAnalyticsSummary.ts
@@ -10,6 +10,7 @@ export type DbAnalyticsSummary = {
avg_messages: number;
concentration: number;
median_messages: number;
+ models: any[] | null;
most_active_project: string;
p90_messages: number;
token_reporting_sessions: number;
diff --git a/frontend/src/lib/api/generated/services/AnalyticsService.test.ts b/frontend/src/lib/api/generated/services/AnalyticsService.test.ts
new file mode 100644
index 000000000..e704ba8c0
--- /dev/null
+++ b/frontend/src/lib/api/generated/services/AnalyticsService.test.ts
@@ -0,0 +1,48 @@
+import {
+ describe,
+ expect,
+ it,
+ vi,
+ beforeEach,
+} from "vite-plus/test";
+
+const { request } = vi.hoisted(() => ({
+ request: vi.fn(),
+}));
+
+vi.mock("../core/OpenAPI", () => ({
+ OpenAPI: {},
+}));
+
+vi.mock("../core/request", () => ({
+ request,
+}));
+
+import { AnalyticsService } from "./AnalyticsService";
+
+describe("AnalyticsService signal sessions", () => {
+ beforeEach(() => {
+ request.mockReset();
+ request.mockResolvedValue({});
+ });
+
+ it("includes the model filter in signal session requests", async () => {
+ await AnalyticsService.getApiV1AnalyticsSignalSessions({
+ signal: "runaway_tool_loop_count",
+ from: "2024-06-01",
+ to: "2024-06-07",
+ timezone: "UTC",
+ model: "gpt-4o",
+ });
+
+ expect(request).toHaveBeenCalledWith(
+ {},
+ expect.objectContaining({
+ query: expect.objectContaining({
+ signal: "runaway_tool_loop_count",
+ model: "gpt-4o",
+ }),
+ }),
+ );
+ });
+});
diff --git a/frontend/src/lib/api/generated/services/AnalyticsService.ts b/frontend/src/lib/api/generated/services/AnalyticsService.ts
index 4e7868ece..90076aeec 100644
--- a/frontend/src/lib/api/generated/services/AnalyticsService.ts
+++ b/frontend/src/lib/api/generated/services/AnalyticsService.ts
@@ -30,6 +30,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -64,6 +65,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -111,6 +116,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -148,6 +154,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -182,6 +189,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -229,6 +240,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -266,6 +278,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -299,6 +312,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -342,6 +359,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -378,6 +396,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -411,6 +430,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -454,6 +477,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -490,6 +514,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -523,6 +548,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -566,6 +595,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -603,6 +633,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -641,6 +672,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Filter by model
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -688,6 +723,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -726,6 +762,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -759,6 +796,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -802,6 +843,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -838,6 +880,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -871,6 +914,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -914,6 +961,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -950,6 +998,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -983,6 +1032,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -1026,6 +1079,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -1062,6 +1116,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -1095,6 +1150,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -1138,6 +1197,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -1174,6 +1234,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -1208,6 +1269,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -1255,6 +1320,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
@@ -1292,6 +1358,7 @@ export class AnalyticsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -1325,6 +1392,10 @@ export class AnalyticsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -1368,6 +1439,7 @@ export class AnalyticsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
diff --git a/frontend/src/lib/api/generated/services/TrendsService.ts b/frontend/src/lib/api/generated/services/TrendsService.ts
index fd4430e27..47b0cea01 100644
--- a/frontend/src/lib/api/generated/services/TrendsService.ts
+++ b/frontend/src/lib/api/generated/services/TrendsService.ts
@@ -19,6 +19,7 @@ export class TrendsService {
machine,
project,
agent,
+ model,
dow,
hour,
minUserMessages,
@@ -54,6 +55,10 @@ export class TrendsService {
* Filter by agent
*/
agent?: string,
+ /**
+ * Comma-separated model filter
+ */
+ model?: string,
/**
* Day of week, Monday=0 through Sunday=6
*/
@@ -105,6 +110,7 @@ export class TrendsService {
'machine': machine,
'project': project,
'agent': agent,
+ 'model': model,
'dow': dow,
'hour': hour,
'min_user_messages': minUserMessages,
diff --git a/frontend/src/lib/api/types/analytics.ts b/frontend/src/lib/api/types/analytics.ts
index 7c902429e..700e6af46 100644
--- a/frontend/src/lib/api/types/analytics.ts
+++ b/frontend/src/lib/api/types/analytics.ts
@@ -22,6 +22,7 @@ export interface AnalyticsSummary {
total_messages: number;
total_output_tokens?: number;
token_reporting_sessions?: number;
+ models?: string[];
active_projects: number;
active_days: number;
avg_messages: number;
diff --git a/frontend/src/lib/components/analytics/ActiveFilters.svelte b/frontend/src/lib/components/analytics/ActiveFilters.svelte
index 81da8a157..50e589b3e 100644
--- a/frontend/src/lib/components/analytics/ActiveFilters.svelte
+++ b/frontend/src/lib/components/analytics/ActiveFilters.svelte
@@ -3,6 +3,7 @@
import {
CalendarIcon,
ClockIcon,
+ CodeIcon,
FolderIcon,
MessageSquareTextIcon,
MonitorIcon,
@@ -16,6 +17,11 @@
? analytics.agent.split(",")
: [],
);
+ const selectedModels = $derived(
+ analytics.model
+ ? analytics.model.split(",").filter((model) => model.length > 0)
+ : [],
+ );
const selectedMachines = $derived(
analytics.machine
? analytics.machine.split(",")
@@ -78,6 +84,7 @@
(analytics.project !== "" ? 1 : 0) +
selectedMachines.length +
selectedAgents.length +
+ selectedModels.length +
selectedStatuses.length +
(analytics.minUserMessages > 0 ? 1 : 0) +
(!analytics.includeOneShot ? 1 : 0) +
@@ -156,6 +163,20 @@
{/each}
+ {#each selectedModels as model (model)}
+
+ {/each}
+
{#if analytics.minUserMessages > 0}