diff --git a/lib/captain/summary_service.rb b/lib/captain/summary_service.rb
index 1e1e24f76..eb5309cb6 100644
--- a/lib/captain/summary_service.rb
+++ b/lib/captain/summary_service.rb
@@ -26,11 +26,12 @@ class Captain::SummaryService < Captain::BaseTaskService
end
def generate_and_cache_summary
+ msg_count = conversation_message_count
result = make_api_call(
- model: GPT_MODEL,
+ model: summary_model(msg_count),
messages: [
{ role: 'system', content: prompt_from_file('summary') },
- { role: 'user', content: conversation.to_llm_text(include_contact_details: false) }
+ { role: 'user', content: build_summary_content(msg_count) }
]
)
@@ -39,6 +40,41 @@ class Captain::SummaryService < Captain::BaseTaskService
result
end
+ def conversation_message_count
+ conversation.messages.where(message_type: [:incoming, :outgoing]).count
+ end
+
+ def summary_model(msg_count)
+ msg_count < 7 ? GPT_MODEL : 'gpt-4.1'
+ end
+
+ def build_summary_content(msg_count)
+ llm_text = conversation.to_llm_text(include_contact_details: false)
+ context = build_conversation_context(msg_count)
+ context.present? ? "#{llm_text}\n\n#{context}" : llm_text
+ end
+
+ def build_conversation_context(msg_count)
+ ['Conversation Stats:', *context_fields(msg_count).compact].join("\n")
+ end
+
+ def context_fields(msg_count)
+ [
+ "Message count: #{msg_count}",
+ ("Status: #{conversation.status}" if conversation.status.present?),
+ ("Priority: #{conversation.priority}" if conversation.priority.present?),
+ ("Labels: #{conversation.cached_label_list}" if conversation.cached_label_list.present?),
+ *account_context_fields
+ ]
+ end
+
+ def account_context_fields
+ [
+ ("Account industry: #{account.custom_attributes['industry']}" if account.custom_attributes&.dig('industry').present?),
+ ("Account language: #{account.locale}" if account.locale.present?)
+ ]
+ end
+
def cache_summary(summary)
conversation.update(
cached_summary: summary,
diff --git a/lib/integrations/openai/openai_prompts/summary.liquid b/lib/integrations/openai/openai_prompts/summary.liquid
index f14f73cc5..9b8778070 100644
--- a/lib/integrations/openai/openai_prompts/summary.liquid
+++ b/lib/integrations/openai/openai_prompts/summary.liquid
@@ -2,19 +2,29 @@
AI support conversation summarizer
- Produce a high-signal summary that lets a new agent understand the CURRENT STATE in under 10 seconds.
+ Produce a high-signal bullet summary that lets a new agent understand the CURRENT STATE and know what to do next — in under 10 seconds.
+ Earlier bullets convey what happened and what the current state is. The final bullet is always the actionable next step.
bullet_list
true
- 10
+
+ Count the messages in the conversation to determine bullet limits.
+ 1-3 messages → 1 bullet max
+ 4-10 messages → 2-3 bullets max
+ 11-30 messages → 3-4 bullets max
+ 31+ messages → 5-6 bullets max
+
true
true
true
+ Do not prefix bullets with labels like "Next step:" or "Issue:" — ordering alone conveys structure.
- same_as_user
+
+ If an agent locale is provided in the context, write the summary in that language.
+
true
true
@@ -27,27 +37,35 @@
- What is the customer trying to achieve right now?
- What is the current blocker/problem?
- What has already been done that changed the state?
- - What is the explicitly stated next step (who does what)?
+ - What is the actionable next step (who does what)?
+ If a priority item has no evidence in the conversation, skip it entirely — do not invent or guess.
+ The final bullet must always be an actionable next step derived from the conversation.
+
+ If account industry or business context is provided, adapt the summary focus accordingly (e.g., e-commerce → order/payment/shipping; SaaS → features/bugs/subscriptions; healthcare → appointments/records).
+ If conversation labels are provided, use them to understand the issue category and focus the summary on what matters for that category.
+ Only use context that is explicitly provided. Do not assume industry or category if not stated.
+
+
A bullet must describe a blocker, a confirmed action taken, a decision, or a required next step.
- If a detail does not help an agent act or understand the blocker, omit it.
+ Would removing this bullet prevent the agent from acting correctly? If no, omit it.
- Greetings, thanks, apologies, pleasantries, offers of help.
- Repeated statements that do not add new information.
- - Meta commentary like “the current blocker is…” if it repeats an earlier bullet.
+ - Meta commentary like "the current blocker is…" if it repeats an earlier bullet.
- Use ONLY information explicitly present in the conversation.
- - Do NOT compute or derive facts (no time math like “15 minutes from now”, no inferred causes).
- - Do NOT invent resolution, closure, or “no further steps” statements.
+ - Do NOT compute or derive facts (no time math like "15 minutes from now", no inferred causes).
+ - Do NOT invent resolution, closure, or "no further steps" statements.
- Do NOT restate the same fact in multiple bullets; merge into one.
@@ -61,7 +79,7 @@
- Prefer **Customer** and **Agent** over “user” and “support agent”.
+ Prefer **Customer** and **Agent** over "user" and "support agent".
@@ -71,7 +89,9 @@
- Every bullet contains at least one **bold** phrase.
- No bullet contains inferred/derived info.
- No two bullets repeat the same meaning.
- - Bullets reflect current state and next action (if explicitly stated).
+ - Last bullet is an actionable next step.
+ - Summary language matches agent locale (if provided) or conversation language.
+ - Bullet count respects the scaling rules for the message count.
true