fix: wire generated_by_model into observation write path

The generated_by_model column was added to the observations table in the
Phase 0 governance schema migration but never wired into the INSERT
statements. All 3,878+ observations in production have this field NULL.

This fix threads the model ID from each agent (SDKAgent, GeminiAgent,
OpenRouterAgent) through processAgentResponse() into storeObservation(),
storeObservations(), and storeObservationsAndMarkComplete().

Unblocks Thompson Sampling RFC (#1571) which needs {obs_type}:{model}
as the bandit arm key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alessandro Costa
2026-04-04 21:25:18 -03:00
parent 811c94da36
commit c3e5f3a79e
5 changed files with 40 additions and 21 deletions
+21 -12
View File
@@ -1517,7 +1517,8 @@ export class SessionStore {
}, },
promptNumber?: number, promptNumber?: number,
discoveryTokens: number = 0, discoveryTokens: number = 0,
overrideTimestampEpoch?: number overrideTimestampEpoch?: number,
generatedByModel?: string
): { id: number; createdAtEpoch: number } { ): { id: number; createdAtEpoch: number } {
// Use override timestamp if provided (for processing backlog messages with original timestamps) // Use override timestamp if provided (for processing backlog messages with original timestamps)
const timestampEpoch = overrideTimestampEpoch ?? Date.now(); const timestampEpoch = overrideTimestampEpoch ?? Date.now();
@@ -1533,8 +1534,9 @@ export class SessionStore {
const stmt = this.db.prepare(` const stmt = this.db.prepare(`
INSERT INTO observations INSERT INTO observations
(memory_session_id, project, type, title, subtitle, facts, narrative, concepts, (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) files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) generated_by_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`); `);
const result = stmt.run( const result = stmt.run(
@@ -1552,7 +1554,8 @@ export class SessionStore {
discoveryTokens, discoveryTokens,
contentHash, contentHash,
timestampIso, timestampIso,
timestampEpoch timestampEpoch,
generatedByModel || null
); );
return { return {
@@ -1651,7 +1654,8 @@ export class SessionStore {
} | null, } | null,
promptNumber?: number, promptNumber?: number,
discoveryTokens: number = 0, discoveryTokens: number = 0,
overrideTimestampEpoch?: number overrideTimestampEpoch?: number,
generatedByModel?: string
): { observationIds: number[]; summaryId: number | null; createdAtEpoch: number } { ): { observationIds: number[]; summaryId: number | null; createdAtEpoch: number } {
// Use override timestamp if provided // Use override timestamp if provided
const timestampEpoch = overrideTimestampEpoch ?? Date.now(); const timestampEpoch = overrideTimestampEpoch ?? Date.now();
@@ -1665,8 +1669,9 @@ export class SessionStore {
const obsStmt = this.db.prepare(` const obsStmt = this.db.prepare(`
INSERT INTO observations INSERT INTO observations
(memory_session_id, project, type, title, subtitle, facts, narrative, concepts, (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) files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) generated_by_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`); `);
for (const observation of observations) { for (const observation of observations) {
@@ -1693,7 +1698,8 @@ export class SessionStore {
discoveryTokens, discoveryTokens,
contentHash, contentHash,
timestampIso, timestampIso,
timestampEpoch timestampEpoch,
generatedByModel || null
); );
observationIds.push(Number(result.lastInsertRowid)); observationIds.push(Number(result.lastInsertRowid));
} }
@@ -1780,7 +1786,8 @@ export class SessionStore {
_pendingStore: PendingMessageStore, _pendingStore: PendingMessageStore,
promptNumber?: number, promptNumber?: number,
discoveryTokens: number = 0, discoveryTokens: number = 0,
overrideTimestampEpoch?: number overrideTimestampEpoch?: number,
generatedByModel?: string
): { observationIds: number[]; summaryId?: number; createdAtEpoch: number } { ): { observationIds: number[]; summaryId?: number; createdAtEpoch: number } {
// Use override timestamp if provided // Use override timestamp if provided
const timestampEpoch = overrideTimestampEpoch ?? Date.now(); const timestampEpoch = overrideTimestampEpoch ?? Date.now();
@@ -1794,8 +1801,9 @@ export class SessionStore {
const obsStmt = this.db.prepare(` const obsStmt = this.db.prepare(`
INSERT INTO observations INSERT INTO observations
(memory_session_id, project, type, title, subtitle, facts, narrative, concepts, (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) files_read, files_modified, prompt_number, discovery_tokens, content_hash, created_at, created_at_epoch,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) generated_by_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`); `);
for (const observation of observations) { for (const observation of observations) {
@@ -1822,7 +1830,8 @@ export class SessionStore {
discoveryTokens, discoveryTokens,
contentHash, contentHash,
timestampIso, timestampIso,
timestampEpoch timestampEpoch,
generatedByModel || null
); );
observationIds.push(Number(result.lastInsertRowid)); observationIds.push(Number(result.lastInsertRowid));
} }
+7 -3
View File
@@ -175,7 +175,9 @@ export class GeminiAgent {
worker, worker,
tokensUsed, tokensUsed,
null, null,
'Gemini' 'Gemini',
undefined,
model
); );
} else { } else {
logger.error('SDK', 'Empty Gemini init response - session may lack context', { logger.error('SDK', 'Empty Gemini init response - session may lack context', {
@@ -248,7 +250,8 @@ export class GeminiAgent {
tokensUsed, tokensUsed,
originalTimestamp, originalTimestamp,
'Gemini', 'Gemini',
lastCwd lastCwd,
model
); );
} else { } else {
logger.warn('SDK', 'Empty Gemini observation response, skipping processing to preserve message', { logger.warn('SDK', 'Empty Gemini observation response, skipping processing to preserve message', {
@@ -298,7 +301,8 @@ export class GeminiAgent {
tokensUsed, tokensUsed,
originalTimestamp, originalTimestamp,
'Gemini', 'Gemini',
lastCwd lastCwd,
model
); );
} else { } else {
logger.warn('SDK', 'Empty Gemini summary response, skipping processing to preserve message', { logger.warn('SDK', 'Empty Gemini summary response, skipping processing to preserve message', {
+6 -3
View File
@@ -131,7 +131,8 @@ export class OpenRouterAgent {
tokensUsed, tokensUsed,
null, null,
'OpenRouter', 'OpenRouter',
undefined // No lastCwd yet - before message processing undefined, // No lastCwd yet - before message processing
model
); );
} else { } else {
logger.error('SDK', 'Empty OpenRouter init response - session may lack context', { logger.error('SDK', 'Empty OpenRouter init response - session may lack context', {
@@ -202,7 +203,8 @@ export class OpenRouterAgent {
tokensUsed, tokensUsed,
originalTimestamp, originalTimestamp,
'OpenRouter', 'OpenRouter',
lastCwd lastCwd,
model
); );
} else if (message.type === 'summarize') { } else if (message.type === 'summarize') {
@@ -244,7 +246,8 @@ export class OpenRouterAgent {
tokensUsed, tokensUsed,
originalTimestamp, originalTimestamp,
'OpenRouter', 'OpenRouter',
lastCwd lastCwd,
model
); );
} }
} }
+2 -1
View File
@@ -270,7 +270,8 @@ export class SDKAgent {
discoveryTokens, discoveryTokens,
originalTimestamp, originalTimestamp,
'SDK', 'SDK',
cwdTracker.lastCwd cwdTracker.lastCwd,
modelId
); );
} }
@@ -54,7 +54,8 @@ export async function processAgentResponse(
discoveryTokens: number, discoveryTokens: number,
originalTimestamp: number | null, originalTimestamp: number | null,
agentName: string, agentName: string,
projectRoot?: string projectRoot?: string,
modelId?: string
): Promise<void> { ): Promise<void> {
// Track generator activity for stale detection (Issue #1099) // Track generator activity for stale detection (Issue #1099)
session.lastGeneratorActivity = Date.now(); session.lastGeneratorActivity = Date.now();
@@ -102,7 +103,8 @@ export async function processAgentResponse(
summaryForStore, summaryForStore,
session.lastPromptNumber, session.lastPromptNumber,
discoveryTokens, discoveryTokens,
originalTimestamp ?? undefined originalTimestamp ?? undefined,
modelId
); );
// Log storage result with IDs for end-to-end traceability // Log storage result with IDs for end-to-end traceability