Skip to content

fix: apply custom prompts to new sessions#301

Open
mason5052 wants to merge 2 commits intovxcontrol:mainfrom
mason5052:codex/issue-300-custom-prompts
Open

fix: apply custom prompts to new sessions#301
mason5052 wants to merge 2 commits intovxcontrol:mainfrom
mason5052:codex/issue-300-custom-prompts

Conversation

@mason5052
Copy link
Copy Markdown
Contributor

@mason5052 mason5052 commented May 6, 2026

Summary

Custom system prompts saved via the Settings -> Prompts UI are now applied to newly created assistant and flow sessions. Prior to this change, every session creation path used templates.NewDefaultPrompter() (with a leftover TODO comment), so user customizations never reached the agents and Langfuse traces always showed the default templates.

Problem

backend/pkg/controller/assistant.go (NewAssistantWorker, LoadAssistantWorker) and backend/pkg/controller/flow.go (NewFlowWorker, LoadFlowWorker) all built the prompter from templates.NewDefaultPrompter() and ignored the per-user prompts table that the Prompts UI persists. The Custom badge in the UI was therefore decorative -- saved overrides had no effect on subsequent sessions.

Solution

Introduce a small controller-side helper, newUserPrompter, which:

  1. Loads the user's saved prompts via database.Querier.GetUserPrompts.
  2. Loads the compiled defaults via templates.LoadDefaultPromptsMap() (a new helper that returns the embedded defaults as a PromptsMap directly, avoiding a JSON round-trip) and overlays each non-empty user override onto the resulting map.
  3. Returns a templates.NewFlowPrompter(merged) so prompt types the user never customized continue to resolve to the defaults.

A database error fails session creation explicitly (wrapErrorEndSpan on the existing langfuse span) instead of silently falling back to defaults. Empty bodies are skipped because the UI uses delete (or reset, which writes the default body back) to remove a customization, so an empty row is unexpected and would otherwise surface as ErrTemplateNotFound deep inside agent rendering.

The helper lives in pkg/controller rather than pkg/templates to avoid creating a new templates -> database import edge.

To avoid an N+1 pattern on flow load, assistantWorkerCtx carries an optional prompter field. LoadFlowWorker builds the user prompter once for the flow and reuses it across every assistant in that flow; LoadAssistantWorker falls back to building its own prompter when invoked outside a flow load.

Lifecycle Semantics

  • The user prompter is snapshotted at session creation time (NewFlowWorker, LoadFlowWorker, NewAssistantWorker, standalone LoadAssistantWorker).
  • Customizations edited mid-session are not applied retroactively to running sessions; only sessions created or loaded after the edit pick them up. This matches the existing per-session lifecycle of every other agent setting and is intentional -- live mutation of the prompter would race with in-flight agent calls.
  • Within a single flow load, all assistants share the same snapshot, so every assistant in a given flow sees a consistent set of prompts even if the user edits between assistants.

User Impact

  • New assistant and flow sessions now use the saved custom prompts for each PromptType the user has overridden.
  • Sessions for users who have not customized any prompts behave exactly as before.
  • No GraphQL schema, REST API, database migration, or frontend change.

Test Plan

  • go test ./pkg/controller/... ./pkg/templates/... (passes locally on Go 1.24)
  • New unit tests in backend/pkg/controller/prompter_test.go cover:
    • No user prompts -> defaults preserved
    • Single override -> custom body returned, unrelated types still default
    • Partial overrides on multiple types -> overridden types custom, others default
    • Empty body row -> default preserved (override skipped)
    • DB error from GetUserPrompts -> wrapped error propagated, nil prompter returned
    • Happy path through newUserPrompter end-to-end with a fake database.Querier

Follow-ups

  • backend/cmd/ftester/worker/tester.go carries the same TODO and was intentionally left alone here -- it is a developer harness rather than a session creation path. Happy to fold it in if reviewers prefer one place.

Closes #300

Custom prompts saved via the Settings -> Prompts UI are persisted to the
database, but every assistant and flow session creation path was using
templates.NewDefaultPrompter() with a leftover TODO, so user overrides
never reached the agents and Langfuse traces always showed the defaults.

Add a controller-side helper that loads the user's saved prompts and
overlays them on the compiled defaults. Prompt types the user has not
customized continue to use the defaults; an empty body row is treated
as no override (the UI uses delete to reset). A database error fails
session creation explicitly instead of silently falling back.

Wire the helper into the four affected call sites in NewAssistantWorker,
LoadAssistantWorker, NewFlowWorker, and LoadFlowWorker.

Closes vxcontrol#300

Signed-off-by: mason5052 <ehehwnwjs5052@gmail.com>
Copilot AI review requested due to automatic review settings May 6, 2026 22:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a long-standing bug where user-customized system prompts (saved via Settings → Prompts) were never applied to newly created assistant and flow sessions, because controllers always instantiated templates.NewDefaultPrompter().

Changes:

  • Added controller-side prompter construction that loads per-user prompt overrides from the DB and overlays them onto compiled defaults.
  • Updated assistant + flow worker creation/loading paths to use the user-aware prompter and to fail explicitly on DB prompt-load errors.
  • Added unit tests covering default behavior, overrides, empty-body handling, and DB error propagation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
backend/pkg/controller/prompter.go Adds newUserPrompter / buildUserPrompter to merge user overrides with default templates and return a flow prompter.
backend/pkg/controller/prompter_test.go Adds unit tests validating merge behavior, fallback to defaults, and error propagation.
backend/pkg/controller/flow.go Switches flow worker creation/loading to use newUserPrompter instead of NewDefaultPrompter().
backend/pkg/controller/assistant.go Switches assistant worker creation/loading to use newUserPrompter instead of NewDefaultPrompter().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/pkg/controller/prompter.go Outdated
Comment thread backend/pkg/controller/flow.go
Comment thread backend/pkg/controller/assistant.go Outdated
Address two reviewer concerns on the issue vxcontrol#300 follow-up:

* Replace the `DumpTemplates()` -> `json.Unmarshal` round-trip in
  buildUserPrompter with a new `templates.LoadDefaultPromptsMap()`
  helper that returns the embedded defaults as a `PromptsMap`
  directly. `defaultPrompter.DumpTemplates()` now delegates to the
  same helper, so the JSON output for that API is unchanged.
* Add an optional `prompter` field to `assistantWorkerCtx`. When
  set, `LoadAssistantWorker` reuses it instead of re-querying
  `GetUserPrompts` and re-merging defaults. `LoadFlowWorker`
  populates it once per flow load so multi-assistant flows pay the
  DB+merge cost a single time.

Tests updated to match the new pure-merge `buildUserPrompter`
signature; behavior for users with no overrides is unchanged.

Signed-off-by: mason5052 <ehehwnwjs5052@gmail.com>
@mason5052
Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit (3278c62) addressing both Copilot review comments:

1. JSON round-trip in buildUserPrompter -- Added a new templates.LoadDefaultPromptsMap() helper that returns the embedded defaults as a PromptsMap directly. buildUserPrompter is now a pure map-merge step with no encoding/json involvement. defaultPrompter.DumpTemplates() was refactored to delegate to the same helper, so the JSON output exposed through the existing API is byte-identical (still alphabetical order from embed.FS.ReadDir).

2. N+1 prompter loads in LoadFlowWorker -> LoadAssistantWorker -- Added an optional prompter templates.Prompter field to assistantWorkerCtx. LoadFlowWorker builds the user prompter once for the flow and threads it through to every assistant in the flow load. LoadAssistantWorker reuses it when present and falls back to newUserPrompter(...) when invoked outside a flow load (i.e., the existing standalone code path keeps working unchanged). Net effect: a flow with N assistants now does one GetUserPrompts query + one default-template merge instead of N+1.

Also updated the PR description to make the per-session snapshot semantics explicit: customizations edited mid-session are not applied retroactively; only sessions created/loaded after the edit pick them up. This is unchanged behavior, but worth calling out so future readers don't expect live mutation.

go test ./pkg/controller/... ./pkg/templates/... is green.

@jakgeg009
Copy link
Copy Markdown

Hi, thanks for proposing those changes so quickly. Are there any updates on when this will be merged with main, to update Docker Hub with the new image. It would be great to be able to customize the system prompts. Seems like a pretty basic feature, but would be very helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Custom system prompts saved via Prompts section are not applied to sessions

3 participants