feat: add agent_metrics table schema and Drizzle migration

Adds the agentMetrics table to SQLite schema for storing pre-computed
per-agent event counts (questions, subagents, compactions), enabling
listForRadar to fetch one row per agent instead of scanning log chunks.

Also fixes pre-existing Drizzle snapshot chain collision in meta/
(0035/0036 snapshots had wrong prevId due to parallel agent branches)
to unblock drizzle-kit generate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas May
2026-03-06 21:25:38 +01:00
parent c150f26d4a
commit 276c342a50
7 changed files with 3122 additions and 194 deletions

View File

@@ -0,0 +1,48 @@
import { describe, it, expect } from 'vitest';
import { createTestDatabase } from './test-helpers.js';
import { agentMetrics } from '../../schema.js';
describe('agentMetrics table', () => {
it('select from empty agentMetrics returns []', async () => {
const db = createTestDatabase();
const rows = await db.select().from(agentMetrics);
expect(rows).toEqual([]);
});
it('insert and select a metrics row round-trips correctly', async () => {
const db = createTestDatabase();
await db.insert(agentMetrics).values({
agentId: 'agent-abc',
questionsCount: 3,
subagentsCount: 1,
compactionsCount: 0,
updatedAt: new Date('2024-01-01T00:00:00Z'),
});
const rows = await db.select().from(agentMetrics);
expect(rows).toHaveLength(1);
expect(rows[0].agentId).toBe('agent-abc');
expect(rows[0].questionsCount).toBe(3);
expect(rows[0].subagentsCount).toBe(1);
expect(rows[0].compactionsCount).toBe(0);
});
it('agentId is primary key — duplicate insert throws', async () => {
const db = createTestDatabase();
await db.insert(agentMetrics).values({
agentId: 'agent-dup',
questionsCount: 0,
subagentsCount: 0,
compactionsCount: 0,
updatedAt: new Date(),
});
await expect(
db.insert(agentMetrics).values({
agentId: 'agent-dup',
questionsCount: 1,
subagentsCount: 0,
compactionsCount: 0,
updatedAt: new Date(),
})
).rejects.toThrow();
});
});

View File

@@ -513,6 +513,21 @@ export const agentLogChunks = sqliteTable('agent_log_chunks', {
export type AgentLogChunk = InferSelectModel<typeof agentLogChunks>;
export type NewAgentLogChunk = InferInsertModel<typeof agentLogChunks>;
// ============================================================================
// AGENT METRICS
// ============================================================================
export const agentMetrics = sqliteTable('agent_metrics', {
agentId: text('agent_id').primaryKey(),
questionsCount: integer('questions_count').notNull().default(0),
subagentsCount: integer('subagents_count').notNull().default(0),
compactionsCount: integer('compactions_count').notNull().default(0),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export type AgentMetrics = InferSelectModel<typeof agentMetrics>;
export type NewAgentMetrics = InferInsertModel<typeof agentMetrics>;
// ============================================================================
// CONVERSATIONS (inter-agent communication)
// ============================================================================

View File

@@ -0,0 +1,7 @@
CREATE TABLE `agent_metrics` (
`agent_id` text PRIMARY KEY NOT NULL,
`questions_count` integer DEFAULT 0 NOT NULL,
`subagents_count` integer DEFAULT 0 NOT NULL,
`compactions_count` integer DEFAULT 0 NOT NULL,
`updated_at` integer NOT NULL
);

View File

@@ -2,7 +2,7 @@
"version": "6",
"dialect": "sqlite",
"id": "c84e499f-7df8-4091-b2a5-6b12847898bd",
"prevId": "5fbe1151-1dfb-4b0c-a7fa-2177369543fd",
"prevId": "443071fe-31d6-498a-9f4a-4a3ff24a46fc",
"tables": {
"accounts": {
"name": "accounts",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -260,6 +260,13 @@
"when": 1772798869413,
"tag": "0036_icy_silvermane",
"breakpoints": true
},
{
"idx": 37,
"version": "6",
"when": 1772828694292,
"tag": "0037_eager_devos",
"breakpoints": true
}
]
}
}