Personalization, User Models & Preference Learning

Published:

A useful agent becomes easier to work with over time. It learns that one user prefers concise answers, another wants implementation details, and a third needs every recommendation framed around regulatory risk. It remembers active projects, recurring constraints, accessibility needs, familiar tools, and the level of explanation that helps each person move forward.

This adaptation requires more than memory. Memory preserves information. A user model organizes evidence about a person and their context. A personalization policy decides which part of that model should influence the current task. Preference learning updates the model from explicit choices and observed outcomes.

The architecture has to make adaptation relevant, explainable, scoped, and reversible. A preference that improves one task can distract from another. A signal from last year can fade. A shared workspace can involve several people. Sensitive observations need clear consent and retention boundaries.

Personalization works best as a governed context layer that applies visible, bounded adaptations to agent behavior.

Memory, User Models, and Personalization #

These concepts serve different roles.

Layer Responsibility Example
Memory Preserve retrievable information “The user works on Project Atlas”
User model Organize evidence, scope, confidence, and time Atlas uses Python 3.12 and strict type checking
Preference model Represent choices among alternatives Prefer pytest examples over unittest
Personalization policy Select relevant model entries for this task Apply Python preferences inside Atlas coding work
Context assembler Convert selected entries into bounded instructions “Use Python 3.12 syntax and pytest in examples”
Feedback learner Update evidence from choices and outcomes Increase support after repeated acceptance

Memory and context engineering explains storage, retrieval, and context budgets. Personalization adds interpretation and policy between retrieval and use.

What Belongs in a User Model #

A user model can contain several categories of information.

Stable Profile Facts #

These facts change slowly and usually come from explicit account data or direct statements:

  • locale, language, and time zone
  • accessibility requirements
  • professional role and domain
  • approved communication channels
  • organization and team membership

Interaction Preferences #

These preferences shape presentation and collaboration:

  • concise or detailed responses
  • examples before theory or theory before examples
  • formal or conversational tone
  • tables, prose, diagrams, or code-first explanations
  • preferred units, date formats, and terminology

Task and Domain Preferences #

These preferences apply within a domain, project, or workflow:

  • programming language and framework choices
  • preferred deployment region
  • document templates and review conventions
  • risk tolerance for recommendations
  • approval thresholds for routine actions

Goals and Active Commitments #

Goals help the agent prioritize:

  • current projects and deadlines
  • learning objectives
  • recurring responsibilities
  • decisions awaiting follow-up
  • accepted plans and unresolved questions

Learned Estimates #

The agent can infer useful working estimates from behavior:

  • familiarity with a topic
  • preferred level of detail
  • recurring tool choices
  • times when proactive reminders are useful
  • categories of suggestion the user usually accepts

Learned estimates carry confidence, evidence, and expiry. Their language should express uncertainty: “often selects,” “recently preferred,” or “likely familiar with.” Explicit statements remain distinct from inferred patterns.

Every Preference Needs Provenance #

A preference record should answer:

  • What is the preference?
  • Who supplied or inferred it?
  • Where does it apply?
  • Which evidence supports it?
  • How confident is the system?
  • When was it observed?
  • When should it expire or be reviewed?
  • Which consent or policy permits retention and use?
key:          response_detail
value:        concise
source:       explicit
scope:        project:atlas
confidence:   1.0
observed_at:  2026-07-18T09:42:00Z
expires_at:   2027-01-18T00:00:00Z
evidence:     user_message:msg-8841
consent:      preference-storage:v2

Provenance supports explanation, conflict resolution, privacy controls, and deletion. It also keeps inferred behavior from gradually turning into an unexplained permanent fact.

Scope Prevents Personalization Leakage #

Preferences naturally belong to different scopes.

global user
   ├── domain:software-engineering
   │      ├── project:atlas
   │      └── project:quartz
   ├── domain:travel
   │      └── trip:sydney-2026
   └── session:current-conversation

A global preference such as metric units can apply widely. A project preference such as “use Terraform” belongs inside one technical project. A travel preference such as “morning flights” belongs to travel planning. A session instruction such as “give me the full explanation today” can temporarily shape the current conversation.

The context assembler resolves the most relevant active scope for each preference key. Scope creates predictable behavior and protects one domain from assumptions learned in another.

Useful scope dimensions include:

  • user, household, team, or organization
  • domain and task type
  • project, account, or workspace
  • device and interaction channel
  • geographic or regulatory region
  • session and current request

Explicit and Inferred Preferences #

Explicit signals come directly from the user or an authoritative configuration:

  • “Use Celsius.”
  • selecting a “concise answers” setting
  • choosing one of several generated drafts
  • setting a default deployment region
  • correcting a stored preference

Inferred signals come from patterns:

  • repeatedly expanding detailed explanations
  • consistently choosing TypeScript examples
  • dismissing reminders sent after work hours
  • accepting recommendations from a particular category
  • rewriting generated text toward a stable tone

Explicit preferences carry strong authority because the person intentionally supplied them. Inferred preferences support gentle adaptation and should remain easy to inspect and correct.

Evidence Quality #

Behavior can have several interpretations. Opening a detailed explanation may indicate interest, uncertainty, or accidental interaction. A single signal provides weak evidence. Repeated signals across comparable contexts provide stronger evidence.

A preference learner should consider:

  • repetition across time
  • consistency across similar tasks
  • strength of the user action
  • availability of alternative explanations
  • recency
  • outcome quality after personalization

The learner can ask for confirmation when an inferred preference becomes influential: “You often choose detailed implementation notes for architecture topics. Should I use that format by default?” Confirmation converts a learned estimate into an explicit preference.

The Preference Lifecycle #

Personalization follows a lifecycle:

observe
   │
   ▼
normalize ──► classify sensitivity and scope
   │
   ▼
store evidence ──► resolve conflicts ──► select for task
                                          │
                                          ▼
                                  assemble context patch
                                          │
                                          ▼
                                    observe outcome
                                          │
                      ┌───────────────────┴───────────────────┐
                      ▼                                       ▼
               strengthen / revise                    expire / delete

Observe #

Collect explicit settings, direct statements, corrections, choices, and task outcomes. Each observation includes provenance and the active context.

Normalize #

Map free-form language to a controlled preference schema. “Keep it short,” “brief answers,” and “just the essentials” can all map to response_detail=concise. Controlled values reduce prompt injection risk and make evaluation possible.

Classify #

Assign scope, sensitivity, retention class, confidence, and consent basis. Sensitive categories can require explicit opt-in and shorter retention.

Resolve #

Choose the effective preference from explicit settings, authoritative imports, inferred evidence, and product defaults.

Apply #

Render resolved preferences through reviewed instruction templates. The context assembler includes only preferences relevant to the current task.

Learn #

Measure whether the personalized behavior improved the outcome. Accepted suggestions, successful task completion, corrections, and direct ratings all provide feedback.

Forget #

Expire stale evidence, delete revoked data, and compact redundant observations. Forgetting keeps the model current and limits privacy exposure.

Conflict Resolution #

Preferences can disagree. The user may globally prefer concise answers and request a detailed explanation for one project. An organization may require formal language while the user prefers a casual tone. Recent behavior may differ from an older explicit setting.

A practical precedence policy is:

  1. safety, legal, and organizational constraints
  2. direct instruction in the current request
  3. explicit preference in the most specific active scope
  4. explicit global preference
  5. authoritative imported settings
  6. inferred preference in the most specific active scope
  7. inferred global preference
  8. product default

Specificity, source authority, recency, and confidence all contribute. The resolver should return both the chosen value and an explanation of why it won.

Some conflicts benefit from a question. If two active users share a session and prefer different output formats, the agent can ask which audience the current artifact serves. Clarification preserves user intent and creates better evidence for future tasks.

Turn Preferences into Safe Context #

Raw preference text should remain data. A template layer converts controlled values into instructions.

Stored preference:
  response_detail = concise

Reviewed instruction template:
  "Lead with the outcome and keep explanations compact."

This design provides three benefits:

  • stored user text stays separated from system instructions
  • preference values have a finite, testable effect
  • prompt changes can be versioned independently from user data

The context patch should stay small. Include only preferences that materially influence the current task. A coding request may need language, framework, test style, and explanation depth. A calendar request may need time zone, working hours, meeting duration, and notification preferences.

User Models and Agent Personas #

The user model represents the person receiving assistance. The agent persona represents the stable behavior and identity of the agent.

Personalization adapts the persona within defined bounds. A teaching agent can vary explanation depth while preserving its core pedagogical principles. A support agent can adapt tone while preserving policy and brand voice. An enterprise agent can use a team's terminology while retaining the same security posture.

This separation keeps personalization coherent. User preferences tune appropriate dimensions, while product identity, safety, and organizational obligations remain stable.

Shared and Multi-User Contexts #

Many tasks involve several people:

  • a household planning travel
  • a team writing a design document
  • a clinician and patient reviewing options
  • a manager approving an employee's request
  • several agents representing different organizations

The runtime should preserve preference ownership. Each observation belongs to a principal or group, and group personalization follows an explicit aggregation policy.

Possible policies include:

  • owner decides
  • current editor decides
  • unanimous agreement for consequential choices
  • accessibility needs receive priority
  • organization policy supplies the shared baseline
  • the agent presents alternatives when preferences remain diverse

Collaborative state can track participants, versions, and decisions. The user-model layer adds ownership and preference semantics to that shared state.

Preference Drift and Temporal Behavior #

Preferences evolve. A beginner becomes an expert. A project changes technology. A user adopts a different schedule. Accessibility and communication needs can also change.

Time-aware models support this evolution through:

  • timestamps on every observation
  • expiry for inferred evidence
  • decay that gradually reduces old support
  • change-point detection for sustained new behavior
  • periodic confirmation for influential preferences
  • version history for explicit corrections

Explicit preferences can remain active until changed while still receiving review prompts at sensible intervals. Inferred preferences benefit from decay. A preference supported by activity last week should usually outweigh a similar signal from two years ago.

Temporal reasoning helps interpret schedules and deadlines. Preference time models focus on how evidence changes in relevance.

Privacy, Consent, and User Control #

A detailed user model can reveal habits, relationships, work patterns, health information, and financial circumstances. Personalization therefore needs visible controls.

Users should be able to:

  • inspect stored profile facts and preferences
  • see whether each entry was explicit, imported, or inferred
  • understand where a preference applies
  • correct values and scope
  • disable categories of learning
  • revoke consent
  • delete individual entries or the complete model
  • export their data

Data minimization improves both privacy and relevance. Store the preference needed for behavior and discard the complete interaction after its retention purpose ends. For example, retain temperature_unit=celsius and release the surrounding weather conversation according to the session policy.

Privacy-preserving agents can keep user models on-device, within a region, or behind a tool boundary. Sensitive attributes can stay local while the cloud agent receives a narrow instruction patch.

A Preference Resolver in Python #

The following standard-library example resolves explicit, imported, inferred, and default observations. It applies scope, recency, expiry, source authority, and evidence strength. The output includes an explanation suitable for a settings or transparency interface.

from __future__ import annotations

import math
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import TypeAlias
from uuid import uuid4


PreferenceValue: TypeAlias = str | int | float | bool


class Source(str, Enum):
    EXPLICIT = "explicit"
    IMPORTED = "imported"
    INFERRED = "inferred"
    DEFAULT = "default"


@dataclass(frozen=True)
class PreferenceObservation:
    observation_id: str
    key: str
    value: PreferenceValue
    source: Source
    scope: str
    confidence: float
    observed_at: datetime
    expires_at: datetime | None = None
    evidence_ref: str = ""
    consent_ref: str = ""

    def active_at(self, now: datetime) -> bool:
        return self.expires_at is None or now < self.expires_at


@dataclass(frozen=True)
class ResolvedPreference:
    key: str
    value: PreferenceValue
    confidence: float
    source: Source
    scope: str
    evidence: tuple[str, ...]
    explanation: str


class PreferenceStore:
    def __init__(self) -> None:
        self._observations: dict[str, PreferenceObservation] = {}

    def add(self, observation: PreferenceObservation) -> None:
        if not 0.0 <= observation.confidence <= 1.0:
            raise ValueError("confidence must be between 0 and 1")
        self._observations[observation.observation_id] = observation

    def delete(self, observation_id: str) -> None:
        self._observations.pop(observation_id, None)

    def delete_scope(self, scope: str) -> None:
        self._observations = {
            observation_id: observation
            for observation_id, observation in self._observations.items()
            if observation.scope != scope
        }

    def candidates(
        self,
        key: str,
        active_scopes: tuple[str, ...],
        now: datetime,
    ) -> list[PreferenceObservation]:
        scope_set = set(active_scopes)
        return [
            observation
            for observation in self._observations.values()
            if observation.key == key
            and observation.scope in scope_set
            and observation.active_at(now)
        ]


class PreferenceResolver:
    def __init__(
        self,
        store: PreferenceStore,
        *,
        inferred_half_life: timedelta = timedelta(days=90),
    ) -> None:
        if inferred_half_life <= timedelta(0):
            raise ValueError("inferred_half_life must be positive")
        self.store = store
        self.half_life_seconds = inferred_half_life.total_seconds()

    def resolve(
        self,
        key: str,
        active_scopes: tuple[str, ...],
        now: datetime,
    ) -> ResolvedPreference | None:
        if not active_scopes:
            raise ValueError("active_scopes must contain at least one scope")

        candidates = self.store.candidates(key, active_scopes, now)
        if not candidates:
            return None

        for source in (Source.EXPLICIT, Source.IMPORTED):
            authoritative = [item for item in candidates if item.source == source]
            if authoritative:
                winner = max(
                    authoritative,
                    key=lambda item: (
                        self._scope_score(item.scope, active_scopes),
                        item.observed_at,
                        item.confidence,
                    ),
                )
                return ResolvedPreference(
                    key=key,
                    value=winner.value,
                    confidence=winner.confidence,
                    source=winner.source,
                    scope=winner.scope,
                    evidence=(winner.observation_id,),
                    explanation=(
                        f"Selected the most specific, recent {source.value} "
                        f"preference in scope {winner.scope}"
                    ),
                )

        inferred = [item for item in candidates if item.source == Source.INFERRED]
        if inferred:
            return self._resolve_inferred(key, inferred, active_scopes, now)

        defaults = [item for item in candidates if item.source == Source.DEFAULT]
        winner = max(
            defaults,
            key=lambda item: (
                self._scope_score(item.scope, active_scopes),
                item.observed_at,
            ),
        )
        return ResolvedPreference(
            key=key,
            value=winner.value,
            confidence=winner.confidence,
            source=winner.source,
            scope=winner.scope,
            evidence=(winner.observation_id,),
            explanation=f"Applied the default for scope {winner.scope}",
        )

    def _resolve_inferred(
        self,
        key: str,
        observations: list[PreferenceObservation],
        active_scopes: tuple[str, ...],
        now: datetime,
    ) -> ResolvedPreference:
        support: dict[PreferenceValue, float] = {}
        evidence: dict[PreferenceValue, list[str]] = {}

        for observation in observations:
            age_seconds = max(
                0.0,
                (now - observation.observed_at).total_seconds(),
            )
            recency = math.pow(0.5, age_seconds / self.half_life_seconds)
            scope_weight = 1.0 + 0.15 * self._scope_score(
                observation.scope,
                active_scopes,
            )
            weight = observation.confidence * recency * scope_weight
            support[observation.value] = support.get(observation.value, 0.0) + weight
            evidence.setdefault(observation.value, []).append(
                observation.observation_id
            )

        winner_value, winner_support = max(
            support.items(),
            key=lambda item: item[1],
        )
        total_support = sum(support.values())
        confidence = winner_support / total_support if total_support else 0.0
        winner_observations = [
            item for item in observations if item.value == winner_value
        ]
        winner_scope = max(
            winner_observations,
            key=lambda item: self._scope_score(item.scope, active_scopes),
        ).scope

        return ResolvedPreference(
            key=key,
            value=winner_value,
            confidence=confidence,
            source=Source.INFERRED,
            scope=winner_scope,
            evidence=tuple(evidence[winner_value]),
            explanation=(
                f"Combined {len(evidence[winner_value])} active observations "
                f"with recency and scope weighting"
            ),
        )

    @staticmethod
    def _scope_score(scope: str, active_scopes: tuple[str, ...]) -> int:
        # active_scopes run from broadest to most specific.
        return active_scopes.index(scope)


now = datetime.now(timezone.utc)
store = PreferenceStore()

store.add(
    PreferenceObservation(
        observation_id=str(uuid4()),
        key="response_detail",
        value="concise",
        source=Source.EXPLICIT,
        scope="global",
        confidence=1.0,
        observed_at=now - timedelta(days=30),
        evidence_ref="settings:response-detail",
        consent_ref="preference-storage:v2",
    )
)
store.add(
    PreferenceObservation(
        observation_id=str(uuid4()),
        key="response_detail",
        value="detailed",
        source=Source.EXPLICIT,
        scope="project:atlas",
        confidence=1.0,
        observed_at=now,
        expires_at=now + timedelta(days=60),
        evidence_ref="message:8841",
        consent_ref="preference-storage:v2",
    )
)

resolved = PreferenceResolver(store).resolve(
    "response_detail",
    active_scopes=("global", "domain:software", "project:atlas"),
    now=now,
)

assert resolved is not None
assert resolved.value == "detailed"
assert resolved.scope == "project:atlas"

The resolver expresses several important rules:

  • active scope filters the candidate set
  • expiry removes stale observations
  • explicit evidence takes precedence over inferred evidence
  • specific scope wins within the same source class
  • inferred evidence combines repeated observations
  • temporal decay lets recent behavior carry more weight
  • every result carries evidence and an explanation

The next layer maps ResolvedPreference values through reviewed templates before adding them to model context.

Applying Preferences Through Templates #

A template registry can constrain how preference values influence behavior:

INSTRUCTION_TEMPLATES: dict[str, dict[PreferenceValue, str]] = {
    "response_detail": {
        "concise": "Lead with the outcome and keep supporting detail compact.",
        "balanced": "Provide the outcome followed by the key reasoning.",
        "detailed": "Include the reasoning, trade-offs, and a concrete example.",
    },
    "temperature_unit": {
        "celsius": "Express temperatures in degrees Celsius.",
        "fahrenheit": "Express temperatures in degrees Fahrenheit.",
    },
}


def render_preference(preference: ResolvedPreference) -> str | None:
    templates = INSTRUCTION_TEMPLATES.get(preference.key, {})
    return templates.get(preference.value)

This approach gives product teams a finite personalization surface. Each preference key and value can be evaluated, localized, versioned, and reviewed for policy impact.

Measuring Personalization Quality #

Personalization should improve outcomes while preserving user control. Evaluation needs several dimensions.

Task Quality #

  • completion rate
  • correction rate
  • time to useful result
  • acceptance of recommendations
  • task-specific accuracy

Preference Accuracy #

  • agreement between resolved and explicitly stated preferences
  • rate of user corrections
  • confidence calibration for inferred preferences
  • scope accuracy
  • stale-preference activation rate

Experience #

  • satisfaction and perceived relevance
  • frequency of preference explanations
  • rate of personalization disablement
  • approval of suggested preference updates
  • clarity of settings and deletion controls

Diversity and Exploration #

A personalization policy should leave room for new options. Occasional exploration can reveal changing preferences and prevent the system from repeatedly choosing a narrow historical pattern. The agent can present alternatives when confidence is moderate or the decision carries meaningful trade-offs.

Offline evaluation uses synthetic profiles, historical consented interactions, and scenario suites. Online evaluation uses controlled experiments with clear user benefit metrics and privacy safeguards. Observability and testing connects preference resolution to the resulting model turns and outcomes.

Cold Start #

A new user begins with sparse evidence. Cold-start personalization can draw from:

  • locale and accessibility settings
  • organization-approved defaults
  • current task context
  • a short optional onboarding flow
  • direct questions at meaningful decision points

Early behavior should stay conservative and transparent. Product defaults carry the experience until repeated signals or explicit choices justify adaptation. High-impact preferences deserve direct confirmation.

The agent can also make local, session-only adaptations. If a user asks several advanced questions in one conversation, the agent can raise the technical level for that session while the long-term model waits for stronger evidence.

Operational Design #

Personalization introduces its own control plane:

  • preference schema and allowed values
  • instruction template versions
  • source and precedence policy
  • confidence and decay parameters
  • sensitive-category classification
  • consent and retention rules
  • evaluation suites
  • migration logic for schema changes

Version resolved preferences and template sets in traces. When behavior changes, operators can identify whether the cause came from new evidence, a resolver policy, a template release, or the underlying model.

Agent lifecycle management should treat personalization policy like prompt and tool configuration: tested, versioned, released gradually, and reversible.

Design Checklist #

Model #

  • Does every entry have source, scope, confidence, time, and provenance?
  • Are explicit facts distinct from learned estimates?
  • Are controlled preference values available for common behaviors?
  • Does each inferred category have an expiry policy?

Resolution #

  • Is precedence between current instructions, explicit settings, inference, and defaults clear?
  • Can the resolver explain every selected value?
  • Do project and domain scopes prevent cross-context leakage?
  • Does temporal decay reflect preference drift?

Application #

  • Are resolved values rendered through reviewed templates?
  • Does the context assembler include only task-relevant preferences?
  • Can users see when personalization materially influenced an answer or action?
  • Do agent persona and organizational policy preserve stable boundaries?

Privacy and Control #

  • Does collection have a clear consent and retention basis?
  • Can users inspect, correct, export, and delete their model?
  • Can learning be disabled by category?
  • Can sensitive preferences stay on-device or behind a tool boundary?

Evaluation #

  • Does personalization improve task outcomes?
  • Are inference confidence and scope accuracy measured?
  • Are corrections converted into resolver tests?
  • Does the policy preserve exploration and user choice?

Conclusion #

Personalization turns interaction history into a carefully governed context layer. Memory supplies observations, the user model organizes evidence, the resolver chooses an effective preference, templates shape model behavior, and feedback updates confidence over time.

The architecture follows a few durable principles:

  • preserve provenance, scope, confidence, and time for every preference
  • give explicit choices greater authority than inferred patterns
  • keep inferred preferences tentative, explainable, and subject to decay
  • apply personalization through controlled, versioned templates
  • separate the user's model from the agent's stable persona and policy
  • make inspection, correction, revocation, and deletion part of the product
  • measure task benefit, preference accuracy, privacy, and user control together

A well-designed user model helps the agent meet each person where they are while keeping the adaptation visible and accountable.