diff --git a/src/services/sqlite/SessionStore.ts b/src/services/sqlite/SessionStore.ts index 110990f7..a234a518 100644 --- a/src/services/sqlite/SessionStore.ts +++ b/src/services/sqlite/SessionStore.ts @@ -1508,7 +1508,8 @@ export class SessionStore { }, promptNumber?: number, discoveryTokens: number = 0, - overrideTimestampEpoch?: number + overrideTimestampEpoch?: number, + generatedByModel?: string ): { id: number; createdAtEpoch: number } { // Use override timestamp if provided (for processing backlog messages with original timestamps) const timestampEpoch = overrideTimestampEpoch ?? Date.now(); @@ -1524,8 +1525,9 @@ export class SessionStore { const stmt = this.db.prepare(` INSERT INTO observations (memory_session_id, project, type, title, subtitle, facts, narrative, concepts, - files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch, + generated_by_model) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( @@ -1543,7 +1545,8 @@ export class SessionStore { discoveryTokens, contentHash, timestampIso, - timestampEpoch + timestampEpoch, + generatedByModel || null ); return { @@ -1642,7 +1645,8 @@ export class SessionStore { } | null, promptNumber?: number, discoveryTokens: number = 0, - overrideTimestampEpoch?: number + overrideTimestampEpoch?: number, + generatedByModel?: string ): { observationIds: number[]; summaryId: number | null; createdAtEpoch: number } { // Use override timestamp if provided const timestampEpoch = overrideTimestampEpoch ?? Date.now(); @@ -1656,8 +1660,9 @@ export class SessionStore { const obsStmt = this.db.prepare(` INSERT INTO observations (memory_session_id, project, type, title, subtitle, facts, narrative, concepts, - files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch, + generated_by_model) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const observation of observations) { @@ -1684,7 +1689,8 @@ export class SessionStore { discoveryTokens, contentHash, timestampIso, - timestampEpoch + timestampEpoch, + generatedByModel || null ); observationIds.push(Number(result.lastInsertRowid)); } @@ -1771,7 +1777,8 @@ export class SessionStore { _pendingStore: PendingMessageStore, promptNumber?: number, discoveryTokens: number = 0, - overrideTimestampEpoch?: number + overrideTimestampEpoch?: number, + generatedByModel?: string ): { observationIds: number[]; summaryId?: number; createdAtEpoch: number } { // Use override timestamp if provided const timestampEpoch = overrideTimestampEpoch ?? Date.now(); @@ -1785,8 +1792,9 @@ export class SessionStore { const obsStmt = this.db.prepare(` INSERT INTO observations (memory_session_id, project, type, title, subtitle, facts, narrative, concepts, - files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch, + generated_by_model) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const observation of observations) { @@ -1813,7 +1821,8 @@ export class SessionStore { discoveryTokens, contentHash, timestampIso, - timestampEpoch + timestampEpoch, + generatedByModel || null ); observationIds.push(Number(result.lastInsertRowid)); } diff --git a/src/services/sqlite/migrations.ts b/src/services/sqlite/migrations.ts index 5be21f6a..76d51a9b 100644 --- a/src/services/sqlite/migrations.ts +++ b/src/services/sqlite/migrations.ts @@ -541,6 +541,37 @@ export const migration008: Migration = { } }; +/** + * Migration 009: Add missing columns to observations table + * + * The generated_by_model column tracks which model generated each observation + * (required for model selection optimization via Thompson Sampling). + * The relevance_count column tracks how many times an observation was reused + * (incremented by the feedback recording pipeline). + * + * Both columns may already exist in databases created by the compiled binary + * (v10.6.3) but are missing from the migration source. This migration + * conditionally adds them. + */ +export const migration009: Migration = { + version: 26, + up: (db: Database) => { + const columns = db.prepare('PRAGMA table_info(observations)').all() as any[]; + const hasGeneratedByModel = columns.some((c: any) => c.name === 'generated_by_model'); + const hasRelevanceCount = columns.some((c: any) => c.name === 'relevance_count'); + + if (!hasGeneratedByModel) { + db.run('ALTER TABLE observations ADD COLUMN generated_by_model TEXT'); + } + if (!hasRelevanceCount) { + db.run('ALTER TABLE observations ADD COLUMN relevance_count INTEGER DEFAULT 0'); + } + }, + down: (_db: Database) => { + // SQLite does not support DROP COLUMN in older versions; no-op + } +}; + /** * All migrations in order */ @@ -552,5 +583,6 @@ export const migrations: Migration[] = [ migration005, migration006, migration007, - migration008 + migration008, + migration009 ]; \ No newline at end of file diff --git a/src/services/worker/GeminiAgent.ts b/src/services/worker/GeminiAgent.ts index 8bb69dc4..0f9a1e4e 100644 --- a/src/services/worker/GeminiAgent.ts +++ b/src/services/worker/GeminiAgent.ts @@ -175,7 +175,9 @@ export class GeminiAgent { worker, tokensUsed, null, - 'Gemini' + 'Gemini', + undefined, + model ); } else { logger.error('SDK', 'Empty Gemini init response - session may lack context', { @@ -248,7 +250,8 @@ export class GeminiAgent { tokensUsed, originalTimestamp, 'Gemini', - lastCwd + lastCwd, + model ); } else { logger.warn('SDK', 'Empty Gemini observation response, skipping processing to preserve message', { @@ -298,7 +301,8 @@ export class GeminiAgent { tokensUsed, originalTimestamp, 'Gemini', - lastCwd + lastCwd, + model ); } else { logger.warn('SDK', 'Empty Gemini summary response, skipping processing to preserve message', { diff --git a/src/services/worker/OpenRouterAgent.ts b/src/services/worker/OpenRouterAgent.ts index e1c77c96..f034987f 100644 --- a/src/services/worker/OpenRouterAgent.ts +++ b/src/services/worker/OpenRouterAgent.ts @@ -131,7 +131,8 @@ export class OpenRouterAgent { tokensUsed, null, 'OpenRouter', - undefined // No lastCwd yet - before message processing + undefined, // No lastCwd yet - before message processing + model ); } else { logger.error('SDK', 'Empty OpenRouter init response - session may lack context', { @@ -202,7 +203,8 @@ export class OpenRouterAgent { tokensUsed, originalTimestamp, 'OpenRouter', - lastCwd + lastCwd, + model ); } else if (message.type === 'summarize') { @@ -244,7 +246,8 @@ export class OpenRouterAgent { tokensUsed, originalTimestamp, 'OpenRouter', - lastCwd + lastCwd, + model ); } } diff --git a/src/services/worker/SDKAgent.ts b/src/services/worker/SDKAgent.ts index 5a9b5866..16efc691 100644 --- a/src/services/worker/SDKAgent.ts +++ b/src/services/worker/SDKAgent.ts @@ -270,7 +270,8 @@ export class SDKAgent { discoveryTokens, originalTimestamp, 'SDK', - cwdTracker.lastCwd + cwdTracker.lastCwd, + modelId ); } diff --git a/src/services/worker/agents/ResponseProcessor.ts b/src/services/worker/agents/ResponseProcessor.ts index 9a059ce6..70360f64 100644 --- a/src/services/worker/agents/ResponseProcessor.ts +++ b/src/services/worker/agents/ResponseProcessor.ts @@ -54,7 +54,8 @@ export async function processAgentResponse( discoveryTokens: number, originalTimestamp: number | null, agentName: string, - projectRoot?: string + projectRoot?: string, + modelId?: string ): Promise { // Track generator activity for stale detection (Issue #1099) session.lastGeneratorActivity = Date.now(); @@ -115,7 +116,8 @@ export async function processAgentResponse( summaryForStore, session.lastPromptNumber, discoveryTokens, - originalTimestamp ?? undefined + originalTimestamp ?? undefined, + modelId ); // Log storage result with IDs for end-to-end traceability