authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-04 15:51:56-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-04 15:51:56-07:00
log2fa1fec8d8f63bc8fa4579e7c0fd280b21de02ef
tree4a81c7657f8d54a33890ced8c46931661d57f001
parent9bd2a4c6886a4c20e0a0a937dc98dd118c645c8f
signature Signed by PGP key B5690EEEBB952194

refactor(server): make title prompts plaintext (#5368)


1 files changed, 98 insertions(+), 104 deletions(-)

apps/server/src/textGeneration/TextGenerationPrompts.ts+98-104
...@@ -150,28 +150,12 @@ export interface BranchNamePromptInput {...@@ -150,28 +150,12 @@ export interface BranchNamePromptInput {
150interface PromptFromMessageInput {150interface PromptFromMessageInput {
151 instruction: string;151 instruction: string;
152 responseShape: string;152 responseShape: string;
153 guidance?: ReadonlyArray<string> | undefined;
154 rulesLabel?: string | undefined;
155 rules: ReadonlyArray<string>;153 rules: ReadonlyArray<string>;
156 afterRules?: ReadonlyArray<string> | undefined;
157 message: string;154 message: string;
158 messageLabel?: string | undefined;
159 preserveMessageEnd?: boolean | undefined;
160 attachments?: ReadonlyArray<ChatAttachment> | undefined;155 attachments?: ReadonlyArray<ChatAttachment> | undefined;
161 additionalInstructions?: string | undefined;156 additionalInstructions?: string | undefined;
162}157}
163158
164function preserveMessageEnd(message: string): string {
165 const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER);
166 const contents = alreadyTruncated
167 ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length)
168 : message;
169 if (!alreadyTruncated && contents.length <= 8_000) {
170 return contents;
171 }
172 return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`;
173}
174
175function buildPromptFromMessage(input: PromptFromMessageInput): string {159function buildPromptFromMessage(input: PromptFromMessageInput): string {
176 const attachmentLines = (input.attachments ?? []).map(160 const attachmentLines = (input.attachments ?? []).map(
177 (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`,161 (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`,
...@@ -180,15 +164,11 @@ function buildPromptFromMessage(input: PromptFromMessageInput): string {...@@ -180,15 +164,11 @@ function buildPromptFromMessage(input: PromptFromMessageInput): string {
180 const promptSections = [164 const promptSections = [
181 input.instruction,165 input.instruction,
182 input.responseShape,166 input.responseShape,
183 ...(input.guidance ?? []),167 "Rules:",
184 input.rulesLabel ?? "Rules:",
185 ...input.rules.map((rule) => `- ${rule}`),168 ...input.rules.map((rule) => `- ${rule}`),
186 ...(input.afterRules ?? []),
187 "",169 "",
188 `${input.messageLabel ?? "User message"}:`,170 "User message:",
189 input.preserveMessageEnd171 limitSection(input.message, 8_000),
190 ? preserveMessageEnd(input.message)
191 : limitSection(input.message, 8_000),
192 ...policyInstruction(input.additionalInstructions),172 ...policyInstruction(input.additionalInstructions),
193 ];173 ];
194 if (attachmentLines.length > 0) {174 if (attachmentLines.length > 0) {
...@@ -234,88 +214,102 @@ export interface ThreadTitlePromptInput {...@@ -234,88 +214,102 @@ export interface ThreadTitlePromptInput {
234 policy?: TextGenerationPolicy | undefined;214 policy?: TextGenerationPolicy | undefined;
235}215}
236216
217// Keep shared editorial rules in these two prompts in sync. Regeneration
218// intentionally adds guidance for thread history and the previous title.
219const INITIAL_THREAD_TITLE_PROMPT = `Generate a title that will help the user recognize this T3 Code thread weeks later.
220Return JSON with exactly one key: title.
221
222Before answering, silently reduce the request to:
223- Subject: What system, feature, or problem is this really about?
224- Outcome: What does the user ultimately want to understand or change?
225- Incidental instructions: What only describes how the agent should do the work?
226
227Title the subject and outcome. Discard incidental instructions.
228
229Editorial rules:
230- 3-8 words, fewer than 40 characters.
231- Use a compact noun phrase or clear action phrase.
232- Capture the umbrella goal when the request lists several symptoms or steps.
233- Name the product change, not the mock, plan, report, branch, or PR used to produce it.
234- Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.
235- For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject.
236- For research, name the question domain rather than the requested research process.
237- Do not claim the work is complete.
238- Do not copy and truncate the user's message.
239- Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation.
240- Use attached images as primary context for UI issues.
241- When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.`;
242
243function regenerateThreadTitlePrompt(previousTitle: string): string {
244 return `Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later.
245The previous title was ${JSON.stringify(previousTitle)}.
246Return JSON with exactly one key: title.
247
248Determine the title in this order:
2491. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about.
2502. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal.
2513. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread.
2524. Title the durable subject and desired outcome, not the current workflow state.
253
254Editorial rules:
255- 3-8 words, fewer than 40 characters.
256- Use a compact noun phrase or clear action phrase.
257- Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail.
258- A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects.
259- Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic.
260- Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.
261- Treat final operational follow-ups and assistant completion summaries as weak evidence of subject.
262- For reviews, name the reviewed feature or system and its durable concern, not one finding from the review.
263- For research, name the question domain rather than the research process.
264- Do not claim the work is complete.
265- Do not copy and truncate a thread message.
266- Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation.
267- Use attached images as primary context for UI issues.
268- When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.
269- Return a meaningfully improved title, not a cosmetic paraphrase of the previous title.
270
271Examples of the distinction:
272- A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review."
273- A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions."
274- A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.`;
275}
276
277function preserveMessageEnd(message: string): string {
278 const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER);
279 const contents = alreadyTruncated
280 ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length)
281 : message;
282 if (!alreadyTruncated && contents.length <= 8_000) {
283 return contents;
284 }
285 return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`;
286}
287
288function threadTitlePromptSuffix(input: ThreadTitlePromptInput): string {
289 const additionalInstructions = policyInstruction(input.policy?.threadTitleInstructions);
290 const attachmentLines = (input.attachments ?? []).map(
291 (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`,
292 );
293
294 let suffix = "";
295 if (additionalInstructions.length > 0) {
296 suffix = `\n${additionalInstructions.join("\n")}`;
297 }
298 if (attachmentLines.length > 0) {
299 suffix += `\n\nAttachment metadata:\n${limitSection(attachmentLines.join("\n"), 4_000)}`;
300 }
301 return suffix;
302}
303
237export function buildThreadTitlePrompt(input: ThreadTitlePromptInput) {304export function buildThreadTitlePrompt(input: ThreadTitlePromptInput) {
238 const isRegeneration = input.previousTitle !== undefined;305 let prompt: string;
239 const prompt = buildPromptFromMessage({306 if (input.previousTitle === undefined) {
240 instruction: isRegeneration307 const message = limitSection(input.message, 8_000);
241 ? [308 prompt = `${INITIAL_THREAD_TITLE_PROMPT}\n\nUser message:\n${message}${threadTitlePromptSuffix(input)}`;
242 "Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later.",309 } else {
243 `The previous title was ${JSON.stringify(input.previousTitle)}.`,310 const message = preserveMessageEnd(input.message);
244 ].join("\n")311 prompt = `${regenerateThreadTitlePrompt(input.previousTitle)}\n\nThread contents:\n${message}${threadTitlePromptSuffix(input)}`;
245 : "Generate a title that will help the user recognize this T3 Code thread weeks later.",312 }
246 responseShape: "Return JSON with exactly one key: title.",
247 guidance: isRegeneration
248 ? [
249 "",
250 "Determine the title in this order:",
251 "1. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about.",
252 "2. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal.",
253 "3. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread.",
254 "4. Title the durable subject and desired outcome, not the current workflow state.",
255 "",
256 ]
257 : [
258 "",
259 "Before answering, silently reduce the request to:",
260 "- Subject: What system, feature, or problem is this really about?",
261 "- Outcome: What does the user ultimately want to understand or change?",
262 "- Incidental instructions: What only describes how the agent should do the work?",
263 "",
264 "Title the subject and outcome. Discard incidental instructions.",
265 "",
266 ],
267 rulesLabel: "Editorial rules:",
268 rules: isRegeneration
269 ? [
270 "3-8 words, fewer than 40 characters.",
271 "Use a compact noun phrase or clear action phrase.",
272 "Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail.",
273 "A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects.",
274 "Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic.",
275 "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.",
276 "Treat final operational follow-ups and assistant completion summaries as weak evidence of subject.",
277 "For reviews, name the reviewed feature or system and its durable concern, not one finding from the review.",
278 "For research, name the question domain rather than the research process.",
279 "Do not claim the work is complete.",
280 "Do not copy and truncate a thread message.",
281 "Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation.",
282 "Use attached images as primary context for UI issues.",
283 "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.",
284 "Return a meaningfully improved title, not a cosmetic paraphrase of the previous title.",
285 ]
286 : [
287 "3-8 words, fewer than 40 characters.",
288 "Use a compact noun phrase or clear action phrase.",
289 "Capture the umbrella goal when the request lists several symptoms or steps.",
290 "Name the product change, not the mock, plan, report, branch, or PR used to produce it.",
291 "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.",
292 'For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject.',
293 "For research, name the question domain rather than the requested research process.",
294 "Do not claim the work is complete.",
295 "Do not copy and truncate the user's message.",
296 "Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation.",
297 "Use attached images as primary context for UI issues.",
298 "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.",
299 ],
300 afterRules: isRegeneration
301 ? [
302 "",
303 "Examples of the distinction:",
304 '- A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review."',
305 '- A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions."',
306 "- A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.",
307 ]
308 : undefined,
309 message: input.message,
310 ...(isRegeneration
311 ? {
312 messageLabel: "Thread contents",
313 preserveMessageEnd: true,
314 }
315 : {}),
316 attachments: input.attachments,
317 additionalInstructions: input.policy?.threadTitleInstructions,
318 });
319 const outputSchema = Schema.Struct({313 const outputSchema = Schema.Struct({
320 title: Schema.String,314 title: Schema.String,
321 });315 });