This document describes the implicit user tone adaptation feature, which allows the system to infer and adapt to each user's communication style.
The system infers a user's preferred communication style from interactions and persists it safely in their profile. Bot responses are then adapted to match the user's tone. The design defends against prompt injection and tool-steering by storing only a fixed, hard-coded vocabulary of tags—never free-form text.
| Tag | Description |
|---|---|
concise |
Short sentences, minimal filler |
detailed |
Slightly more explanation, avoid rambling |
formal |
Professional register and diction |
casual |
Friendly, conversational language |
no_emojis |
Never use emojis |
emojis_ok |
Emojis are welcome where appropriate |
bullet_points |
Prefer bullet points when listing items |
one_question_at_a_time |
Ask only one question per turn |
| Tag | Description |
|---|---|
warm_supportive |
Warm, encouraging stance |
neutral_professional |
Neutral, professional stance (default) |
direct_coach |
Clear, action-oriented feedback |
gentle_coach |
Patient, encouraging guidance |
| Tag | Description |
|---|---|
confirm_before_acting |
Confirm with user before taking actions |
default_actionable |
Provide actionable next steps by default |
high_autonomy |
Act with high autonomy; minimize confirmations |
- concise XOR detailed: At most one may be active. Higher score wins.
- formal XOR casual: At most one may be active. Higher score wins.
- direct_coach XOR gentle_coach: At most one may be active. Higher score wins.
- no_emojis overrides emojis_ok: When no_emojis is active, emojis_ok is forced to 0.
- Default stance: If no stance tag is active, the system defaults to
neutral_professionalin the tone guide.
Tone fields are stored inside the UserProfile struct (serialized as JSON in flow state):
type ProfileTone struct {
Tags []string `json:"tone_tags,omitempty"`
Scores map[string]float32 `json:"tone_scores,omitempty"`
Version int `json:"tone_version"`
LastUpdatedAt time.Time `json:"tone_last_updated_at,omitempty"`
UpdateSource UpdateSource `json:"tone_update_source,omitempty"`
OverrideUntil *time.Time `json:"tone_override_until,omitempty"`
}- Tags: Active subset of whitelist tags.
- Scores: EMA-smoothed scores (0–1) for each observed tag.
- Version: Schema version (currently 1).
- LastUpdatedAt: Timestamp of last tone update.
- UpdateSource:
"explicit"(user requested) or"implicit"(inferred). - OverrideUntil: Optional timestamp for session-level tone override (reserved for future use).
Existing profiles without tone fields load with defaults: empty tags, nil scores, version 1. No database migration is needed because profiles are stored as JSON blobs in the flow_states table.
- Explicit (
tone_update_source: "explicit"): User directly requests a communication style change (e.g., "please be more concise"). Applied immediately with full weight. - Implicit (
tone_update_source: "implicit"): System infers tone from conversation patterns. Applied via EMA smoothing with hysteresis.
- Intake bot: May propose tone updates when explicit request or high-confidence repeated pattern.
- Feedback bot (track_feedback): Primary tone learner. Infers tone from ongoing user language.
- Coordinator: Matches tone in responses but cannot write to user profile.
- Generator: Minimal tone usage; preserves output format.
For implicit updates:
score[tag] = (1 - α) × score[tag] + α × observation
- α = 0.15
- Observation is 1.0 when tag is proposed, 0.0 when absent (decay).
- Activate: Score ≥ 0.7 → tag becomes active.
- Deactivate: Score ≤ 0.4 → tag becomes inactive.
- Between: Tag retains its current state (prevents oscillation).
On implicit updates, non-observed tags decay toward 0 via EMA. This allows tags to deactivate naturally when the user's style changes.
Implicit updates are limited to at most one per 3 minutes per user. Explicit updates bypass this limit.
Even when the LLM proposes tone tags, the server always:
- Strips unknown tags (not in whitelist).
- Clamps scores to [0, 1].
- Applies mutual exclusion rules.
- Enforces rate limits for implicit updates.
- Applies EMA smoothing and hysteresis.
When building system prompts for any bot module, the tone.BuildToneGuide(tags) function generates a compact <TONE POLICY> section appended to the system messages. This section instructs the LLM to:
- Match brevity and formality per active tags.
- Apply the appropriate stance.
- Never mirror hostility, sarcasm, insults, or unsafe language.
The guide is empty when no tone tags are active, adding zero overhead for new users.
- Add the tag string to
AllTagsininternal/tone/tone.go. - If the tag has a mutually exclusive partner, add it to
mutuallyExclusivePairs. - Add a corresponding rule in
BuildToneGuide. - Update the
TestAllTags_Counttest to match the new count. - Update the whitelist description in the bot prompt files.
- Update this document.