Skip to Content
Internal docs are powered by Nextra Docs Theme.
SystemsPlatformAPI2Conversation summary migration

Migrating post-QA conversation summaries to API2

Ownership boundary

When a QA evaluation request completes, Rails still enqueues SummarizeConversationJob. The job no longer calls OpenAI or writes the summary; it is a small, retryable transport bridge to POST /internal/conversation_summaries/enqueue.

API2 owns the rest of the workflow:

QAEvaluationRequest#mark_completed! → GoodJob SummarizeConversationJob (transport only) → POST /internal/conversation_summaries/enqueue → BullMQ conversation-summary job → rebuild Rails-compatible transcript and related-ticket context → GPT-5.6 Luna Flex structured generation → write conversations.summary and conversations.description

The BullMQ job id includes the organization, conversation, and enqueue-time conversation version. A duplicate Rails delivery for the same version therefore collapses onto one job. The worker treats the generated summary as a point-in-time artifact of QA completion and persists it without rebuilding the prompt after the model call. A later QA completion enqueues a new version and replaces the summary.

The internal route requires the shared Rails/API2 bearer token and resolves the organization from its slug before enqueueing. The worker scopes every transcript, context, and update query by the Rails organization id.

Retry and rollback

SummarizeConversationJob retries API2 delivery ten times with polynomial backoff. The API2 job retries generation five times with exponential backoff. Existing GoodJob rows keep their serialized queue name; newly enqueued bridge jobs use default so an API2 outage cannot occupy the latency-sensitive queue.

The old Rails Conversation#summarize implementation remains intact as the rollback path. To roll back before removing it, change SummarizeConversationJob#perform back to conversation.summarize and deploy Rails. API2 jobs already accepted should be allowed to finish; their writes are idempotent replacements of the same two conversation columns.

Do not delete SummarizeConversationJob until unfinished legacy and bridge rows have drained in both regions:

SELECT COUNT(*) FROM good_jobs WHERE serialized_params->>'job_class' = 'SummarizeConversationJob' AND finished_at IS NULL;

After a production bake confirms healthy handoffs, BullMQ completions, summary writes, and model spans in both regions, the remaining Rails model methods and GoodJob bridge can be removed in a follow-up cutover.

September 6 production observation

The first EU observation found summary jobs incorrectly exhausting their stale input retries for conversations with many parts sharing identical provider and database timestamps. Repeated read-only transcript builds returned different hashes at the same byte length because the transcript query did not define an order for complete timestamp ties. The transcript now uses the conversation part id as its final deterministic tie-breaker.

The stale-input fingerprint check was subsequently removed. Rebuilding the full prompt after generation doubled the context-query work and could discard every valid model result when prompt serialization was unstable. It also did not provide true write ordering between independently enqueued versions. Preserve deterministic transcript ordering for reproducible context, but do not reject a valid point-in-time summary solely because a second read differs.

Last updated on