feat: per-user system prompt via compacted user-settings topic - #47
Open
tsuz wants to merge 1 commit into
Open
Conversation
Gated by ENABLE_USER_SETTING (default false). When enabled, the streams
app creates the compacted {AGENT_NAME}-user-settings topic (keyed by
user_id), materializes it as a KTable, and left-joins it into
FullSessionContext during enrichment. The think consumer uses the
joined system_prompt as the base prompt in place of the default
(SYSTEM_PROMPT_FILE / built-in); memoir context is still appended on
top. A tombstone reverts the user to the default prompt.
The enrichment processor now re-keys to user_id once when either memoir
or user settings is enabled, so enabling both costs a single
repartition, and the settings join works with MEMOIR_ENABLED=false.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an optional per-user system prompt override sourced from a new compacted Kafka topic, joins it into the session context during stream enrichment, and applies it in the think consumer as a full replacement for the default base prompt (while still appending memoir context).
Changes:
- Introduces a new compacted
*-user-settingstopic (Topics.USER_SETTINGS) and aUserSettingsmodel, with topology wiring gated byENABLE_USER_SETTING. - Updates the processing enrichment topology to (optionally) left-join user settings by
user_idand carrysystem_promptthroughFullSessionContext. - Updates the think consumer to resolve the base prompt from the per-user override (null/blank fallback) and adds/extends tests for the new behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| think/think-consumer/src/test/java/io/flightdeck/think/consumer/SystemPromptTest.java | Adds tests for per-user base prompt override resolution and composition with memoir. |
| think/think-consumer/src/main/java/io/flightdeck/think/model/FullSessionContext.java | Adds nullable system_prompt to think-side context plus a compatibility constructor. |
| think/think-consumer/src/main/java/io/flightdeck/think/consumer/ThinkConsumer.java | Applies system_prompt override via resolveBasePrompt() when building the system prompt. |
| processor-apps/processing/src/test/java/io/flightdeck/streams/UserSettingTopologyTest.java | New topology-level tests ensuring user-settings wiring is present/absent based on the flag. |
| processor-apps/processing/src/test/java/io/flightdeck/streams/processors/EnrichInputMessageProcessorTest.java | Adds join semantics tests (present/absent/tombstone/isolation/memoir+settings). |
| processor-apps/processing/src/main/java/io/flightdeck/streams/processors/EnrichInputMessageProcessor.java | Adds optional user-settings join keyed by user_id and enriches FullSessionContext.system_prompt. |
| processor-apps/processing/src/main/java/io/flightdeck/streams/model/UserSettings.java | New model for compacted per-user settings records. |
| processor-apps/processing/src/main/java/io/flightdeck/streams/model/FullSessionContext.java | Adds nullable system_prompt to processing-side context. |
| processor-apps/processing/src/main/java/io/flightdeck/streams/FlightDeckStreamsApp.java | Adds ENABLE_USER_SETTING gating, topic creation, and KTable materialization. |
| processor-apps/processing/src/main/java/io/flightdeck/streams/config/Topics.java | Adds the USER_SETTINGS topic constant and documentation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+44
to
+48
| void resolveBasePrompt_nullOrBlank_usesDefault() { | ||
| assertThat(ThinkConsumer.resolveBasePrompt(null)).contains("intelligent AI assistant"); | ||
| assertThat(ThinkConsumer.resolveBasePrompt("")).contains("intelligent AI assistant"); | ||
| assertThat(ThinkConsumer.resolveBasePrompt(" ")).contains("intelligent AI assistant"); | ||
| } |
Comment on lines
116
to
+118
| full.history().size(), | ||
| full.memoirContext() != null, | ||
| full.systemPrompt() != null, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a per-user system prompt override, delivered through a new compacted Kafka topic and joined into the session context — mirroring the existing memoir pattern.
{AGENT_NAME}-user-settings(compacted, keyed byuser_id), value:{"system_prompt": "...", "updated_at": "..."}. A tombstone reverts the user to the default prompt.ENABLE_USER_SETTING(defaultfalse):true→ the streams app creates the topic if missing, materializes it as a KTable, and left-joins it intoFullSessionContextduring enrichment.false→ the topology never references the topic andsystem_promptstays null.system_promptis present and non-blank it replaces the default base prompt (SYSTEM_PROMPT_FILE/ built-in) entirely; memoir context is still appended on top.user_idonce when either memoir or user settings is enabled, so the settings join works withMEMOIR_ENABLED=falseand enabling both costs a single repartition.FullSessionContextmodels gained a nullablesystem_promptfield (@JsonIgnoreProperties(ignoreUnknown=true)on both sides), so mixed old/new deployments are safe. The think-side record keeps a 7-arg compatibility constructor.Settings update semantics
A KTable join uses the table state at message-flow time, so a settings update takes effect from the user's next turn.
Tests
UserSettingTopologyTest(new): flag on / on-without-memoir / off, including topology-description assertions that the store and topic are absent when disabled.EnrichInputMessageProcessorTest: join, absent record, tombstone revert, per-user isolation, memoir+settings combined.SystemPromptTest: override replaces default, null/blank falls back, override composes with memoir.🤖 Generated with Claude Code