PreferenceTools collection manages the decoded hard and soft constraint set for a conversation (the top-left panel in the UI). One instance is created per thread, bound to its thread_id, so every tool reads and writes the preferences for that one conversation without thread_id being an LLM-facing parameter.
Each preference carries a locked flag. The agent path RESPECTS this flag: update_preferences and remove_preferences read the preferences first, and any locked target is skipped and reported (not mutated), with the repository enforcing this via respect_lock=True. The user path (REST endpoints) ignores the lock and can always edit, and it is also where preferences are locked and unlocked.
The mutating tools are batch-shaped: add_preferences and update_preferences take lists, and remove_preferences takes a list of indices, so the agent can act on many preferences in one call (pass a one-element list to act on a single preference). list_preferences is read-only.
Shared conventions (the @workspace_tool contract, the JSON result envelope, and the injected action_and_reasoning argument) are documented in Tools. Every tool below also receives the injected action_and_reasoning argument described there; it is not listed as an argument row.
Source: backend/app/tools/preferences.py
add_preferences
Record one or more decoded preferences (hard or soft constraints) extracted from the user’s message.
Description shown to the agent:
Record one or more decoded user preferences (hard or soft constraints). Use this for every add — pass a one-element list to record a single preference.Arguments
Each item in
items is a preference object:
Returns: a confirmation string with the number of preferences recorded, e.g.
recorded 2 preferences.
Implementation: records the batch via repo.add_preferences_bulk. New preferences are unlocked.
Source: backend/app/tools/preferences.py
list_preferences
Read the current preference / constraint list for this thread.
Description shown to the agent:
List the current preferences / constraints.Arguments None (the agent still passes the injected
action_and_reasoning).
Returns: newline-separated lines of the form #N [kind] title — subtitle, each appended with (locked) when the preference is locked, or a placeholder string when no preferences have been recorded yet.
Implementation: has no args_schema (the decorator defaults to the empty schema). Reads via repo.list_preferences. The (locked) suffix is also added to locked rows in the auto-injected <preferences> workspace block.
Source: backend/app/tools/preferences.py
update_preferences
Edit the title, subtitle, or kind of one or more existing preferences, addressed by their 1-based indices.
Description shown to the agent:
Edit the title, subtitle, or kind of one or more existing preferences by their 1-based indices. Pass a list of {index, title?, subtitle?, kind?}. Locked preferences are skipped and reported so the agent can tell the user.
Arguments
Each item in
updates is an edit object:
Returns: a summary naming the updated preferences, plus any that were locked (skipped and reported, “ask the user to unlock first”) or out of range, or
no matching preferences found when nothing applied.
Implementation: resolves indices against the list as it stands at call time. Locked targets are skipped before mutating; the repository call uses respect_lock=True, so locked items are never modified by the agent path.
Source: backend/app/tools/preferences.py
remove_preferences
Delete one or more preferences, addressed by their 1-based indices.
Description shown to the agent:
Remove one or more preferences by their 1-based indices (as shown byArgumentslist_preferences; pass a list). Locked preferences are skipped and reported. Calllist_preferencesfirst to confirm the indices, and remove only when the user explicitly asks you to drop a preference.
Returns: a summary naming the removed preferences, plus any that were locked (skipped and reported, “ask the user to unlock first”) or out of range, or
no matching preferences found when nothing applied.
Implementation: resolves all targets up front so indices reference the current list. Deletion uses respect_lock=True, so locked items are skipped and reported rather than removed by the agent path.
Source: backend/app/tools/preferences.py