feat(chatd): add user-level custom prompt for agent chats (#22896)

Adds a user-level custom prompt to the database.

I'll be doing a follow-up for the UI, as we currently do not have
user-level settings (it's just admin). I'll also make it very obvious
for chats where there is a user-level prompt, but I don't know how yet.
This commit is contained in:
Kyle Carberry
2026-03-10 11:17:52 -04:00
committed by GitHub
parent 6489d6f714
commit b6d1a11c58
12 changed files with 301 additions and 2 deletions
+23 -2
View File
@@ -2229,9 +2229,11 @@ func (p *Server) runChat(
if instruction := p.resolveInstructions(ctx, chat, getWorkspaceConn); instruction != "" {
prompt = chatprompt.InsertSystem(prompt, instruction)
}
if userPrompt := p.resolveUserPrompt(ctx, chat.OwnerID); userPrompt != "" {
prompt = chatprompt.InsertSystem(prompt, userPrompt)
}
// Use the model config's context_limit as a fallback when the LLM
// provider doesn't include context_limit in its response metadata
// Use the model config's context_limit as a fallback when the LLM // provider doesn't include context_limit in its response metadata
// (which is the common case).
modelConfigContextLimit := modelConfig.ContextLimit
@@ -2510,6 +2512,9 @@ func (p *Server) runChat(
if instruction := p.resolveInstructions(reloadCtx, chat, getWorkspaceConn); instruction != "" {
reloadedPrompt = chatprompt.InsertSystem(reloadedPrompt, instruction)
}
if userPrompt := p.resolveUserPrompt(reloadCtx, chat.OwnerID); userPrompt != "" {
reloadedPrompt = chatprompt.InsertSystem(reloadedPrompt, userPrompt)
}
return reloadedPrompt, nil
},
@@ -2884,6 +2889,22 @@ func (p *Server) resolveInstructions(
return instruction
}
// resolveUserPrompt fetches the user's custom chat prompt from the
// database and wraps it in <user-instructions> tags. Returns empty
// string if no prompt is set.
func (p *Server) resolveUserPrompt(ctx context.Context, userID uuid.UUID) string {
raw, err := p.db.GetUserChatCustomPrompt(ctx, userID)
if err != nil {
// sql.ErrNoRows is the normal "not set" case.
return ""
}
trimmed := strings.TrimSpace(raw)
if trimmed == "" {
return ""
}
return "<user-instructions>\n" + trimmed + "\n</user-instructions>"
}
func (p *Server) recoverStaleChats(ctx context.Context) {
staleAfter := time.Now().Add(-p.inFlightChatStaleAfter)
staleChats, err := p.db.GetStaleChats(ctx, staleAfter)