Goal: Give Gemma a remember_this tool that stores explicit user preferences. Preferences are loaded passively at session start and injected into the system prompt — no recall tool call required. Preferences are keyed to a user_id so they are ready for a multi-user / login system.
search_memory.
User: "remember to make URLs clickable when you present them"
│
└─► Gemma calls remember_this("User wants URLs formatted as clickable Markdown links")
│
└─► PreferenceService.write(user_id, text)
│
└─► ChromaDB user.preferences collection
{user_id, text, timestamp}
On next session start:
GeneratorAgent.__init__ / configure()
│
└─► PreferenceService.load(user_id) → ["User wants URLs as Markdown links", ...]
│
└─► Injected as [User Preferences] block into system prompt
│
└─► Gemma sees it on every turn — no tool call needed
A single user_id is used for all sessions. It is set in config/system.yaml and defaults to "default". This makes the preferences store functional immediately without any auth plumbing.
When a login system is added, the user_id is resolved from the authenticated session at WebSocket connect time and passed through the query.received envelope. GeneratorAgent reads it and loads that user's preferences on each turn (or caches them at session start). No change to PreferenceService or the ChromaDB schema is needed — the user_id key is already the discriminator.
user_id flows as a first-class field through the query pipeline from day one. It just defaults to "default" until login exists. This avoids a future migration of the preference store.
Thin wrapper around a dedicated ChromaDB collection (user.preferences). Separate from MemoryService because preferences have a different access pattern: full-load by user_id, not semantic search.
# src/local/services/preference_service.py
class PreferenceService:
def write(self, user_id: str, text: str) -> str:
"""Store a preference. Returns the preference ID."""
def load(self, user_id: str) -> list[str]:
"""Return all preference texts for user_id, oldest first."""
def delete(self, preference_id: str) -> None:
"""Remove a preference by ID."""
def list(self, user_id: str) -> list[dict]:
"""Return [{id, text, timestamp}] for the user."""
ChromaDB document format:
id: uuid
document: "User wants URLs formatted as clickable Markdown links"
metadata: {user_id: "default", timestamp: 1234567890.0}
No embedding needed for load-by-user_id (metadata filter is exact). Embeddings can be added later if semantic deduplication is wanted.
A standard BaseTool subclass. Gemma calls it when the user says "remember X", "don't forget that I prefer X", etc.
# src/local/tools/remember_this_tool.py # config/remember_this.yaml Tool name: remember_this Parameter: preference (string) — the preference to store, in plain English Bus: tool.call.remember_this → tool.result.remember_this Returns to Gemma: "Preference saved: <text>"
The tool reads user_id from the envelope's session context (defaults to "default" until login is wired). It calls PreferenceService.write(user_id, text) and returns a confirmation string.
At session start (when the first query.received arrives for a new session), GeneratorAgent loads preferences for the current user_id and prepends them to the system prompt as a [User Preferences] block.
# In generator_agent.py, _build_system_prompt() or equivalent:
prefs = self._preference_service.load(user_id)
if prefs:
pref_block = "[User Preferences]\n" + "\n".join(f"- {p}" for p in prefs)
system_prompt = pref_block + "\n\n" + base_system_prompt
Preferences are loaded once per session and cached. If remember_this is called mid-session, the new preference takes effect on the next session (or we can hot-reload — TBD).
| Subject | Publisher | Subscriber |
|---|---|---|
tool.call.remember_this | ToolDispatcher | RememberThisTool |
tool.result.remember_this | RememberThisTool | GeneratorAgent |
tool.activity.remember_this | RememberThisTool | ToolWindow (observer) |
Add TOOL_CALL_REMEMBER_THIS and TOOL_RESULT_REMEMBER_THIS to protocol/subjects.py. Add both to ws_bridge.py CHAT_OBSERVE so the tool chip appears in the web UI.
# config/remember_this.yaml description: | Store a preference that will be applied to every future conversation. Call this when the user says "remember that I...", "always...", "don't forget...", or states a persistent behavioral preference. Do NOT use for factual information or one-time context — use this only for durable preferences about how to respond. param_preference: | The preference to store, written as a complete sentence describing what the user wants. Example: "User wants URLs formatted as Markdown clickable links."
# config/system.yaml (add field) user_id: "default"
| File | Change |
|---|---|
src/local/services/preference_service.py | NEW PreferenceService |
src/local/tools/remember_this_tool.py | NEW RememberThisTool |
config/remember_this.yaml | NEW tool description + param |
config/system.yaml | MOD add user_id: "default" |
src/local/protocol/subjects.py | MOD add TOOL_CALL/RESULT_REMEMBER_THIS |
src/local/agents/generator_agent.py | MOD inject preferences into system prompt |
src/local/run.py | MOD instantiate PreferenceService, inject into tool + generator |
src/local/api/ws_bridge.py | MOD add remember_this to CHAT_OBSERVE |
tests/test_preference_service.py | NEW |
tests/test_remember_this_tool.py | NEW |
forget_this tool) — handled via UI browseruser_id defaults to "default" for nowsubjects.pyPreferenceService + testsRememberThisTool + tests + configuser_id to system.yamlGeneratorAgent to load + inject preferencesrun.py + ws_bridge.py