# Personalized Behavior

> Implements personalized AI agent behavior by learning and adapting to individual user preferences, communication styles, expertise levels, and interaction history for tailored responses.

- Skill: `paulpas/personalized-behavior` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/personalized-behavior`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/personalized-behavior/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/personalized-behavior

---





# Personalized AI Agent Behavior

Implements personalized AI agent behavior by adapting responses to individual users based on learned preferences, communication styles, expertise levels, and interaction history. The model acts as a user-aware assistant that continuously refines its output format, tone, depth, and complexity to match each user's evolving needs and expectations.

## TL;DR Checklist

- [ ] Build or load a UserProfile containing explicit style and preference fields
- [ ] Classify communication style (direct, explanatory, visual, structured) before responding
- [ ] Adjust response depth based on expertise_level (beginner → expert scale)
- [ ] Store interaction history with timestamps for pattern recognition over sessions
- [ ] Apply early-exit guard clauses when profile data is missing or stale
- [ ] Return a new ProfileSnapshot after every significant interaction
- [ ] Reference code-philosophy (5 Laws of Elegant Defense) in all persistence logic

---

## Orchestration Flow

```
User Request
    ↓
┌───────────────────────────────────────────┐
│  Load/Create UserProfile                  │
│  (from persistence or default profile)    │
└──────────────┬────────────────────────────┘
               ↓
┌───────────────────────────────────────────┐
│  Classify Communication Style             │
│  (direct / explanatory / visual / structured)   │
│                                           │
│  <3 messages? ──► Use stored/default      │
│  Enough data? ──► Run classifier          │
└──────────────┬────────────────────────────┘
               ↓
┌───────────────────────────────────────────┐
│  Assess Expertise Level                   │
│  (beginner → expert scale)                │
│                                           │
│  Uncertain? ──► Assume intermediate       │
│  Clear signal? ──► Set detected level     │
└──────────────┬────────────────────────────┘
               ↓
┌───────────────────────────────────────────┐
│  Generate Tailored Response               │
│  (apply tone + depth + style filters)     │
│                                           │
│  Validation fail? ──► Re-generate         │
│  Passes check? ──► Return to user         │
└──────────────┬────────────────────────────┘
               ↓
┌───────────────────────────────────────────┐
│  Update Profile from Interaction          │
│  (refine style/expertise from feedback)   │
│                                           │
│  Changes detected? ──► Persist snapshot   │
│  No changes? ──► Skip write               │
└───────────────────────────────────────────┘
```

## When to Use

Use this skill when:

- An AI agent needs to adapt its responses to multiple distinct users
- Building a persistent assistant that remembers user preferences across sessions
- Designing a system where response tone, depth, or format should vary by audience
- Creating onboarding flows that gradually calibrate to a user's preferred communication style
- Implementing a preference center where users can explicitly set their interaction settings
- Developing a coding assistant that adjusts explanation depth based on the developer's seniority

## When NOT to Use

Avoid this skill for:

- Single-session, one-shot interactions with no continuity need — the profiling overhead is wasted
- Situations requiring identical output for all users (e.g., legal disclaimers, compliance text) — use templated responses instead
- Real-time systems where profile lookup latency would cause unacceptable delays — cache aggressively or skip profiling
- User profiles that are actively being manipulated by untrusted parties — always validate inputs (Law 2)

---

## Core Workflow

1. **Initialize or Load User Profile** — Fetch the user's existing profile from storage, or create a new one with default settings if none exists. Default to conservative assumptions: intermediate expertise, explanatory communication style, and standard formatting preferences.
   **Checkpoint:** Validate that all required profile fields are populated; fill missing fields with documented defaults before proceeding.

2. **Classify Communication Style** — Analyze recent user messages to determine the dominant communication pattern:
   - `direct` — short sentences, action-oriented, minimal preamble
   - `explanatory` — asks "why" questions, wants reasoning and context
   - `visual` — requests diagrams, charts, or structured layouts
   - `structured` — prefers numbered lists, tables, and categorized output
   **Checkpoint:** If fewer than 3 messages exist for classification, fall back to the user's stored preference or the default style.

3. **Assess Expertise Level** — Determine the user's domain expertise on a 5-level scale:
   - `beginner` — needs definitions, step-by-step guidance, no jargon without explanation
   - `intermediate` — understands fundamentals, wants best practices and reasoning
   - `advanced` — knows core concepts, seeks edge cases, performance tradeoffs, internals
   - `expert` — expects minimal scaffolding, prefers raw technical detail and references
   **Checkpoint:** If expertise cannot be confidently assessed from interaction history, conservatively assume `intermediate` and log the uncertainty.

4. **Generate Tailored Response** — Compose the output by applying the user's profile filters:
   - Adjust tone (formal vs. casual) based on preference
   - Select appropriate depth (surface → deep-dive) based on expertise level
   - Format output according to communication style classification
   **Checkpoint:** Run a pre-output validation pass — does this response match the expected tone, depth, and format?

5. **Update Profile from Interaction** — After generating the response, update the user's profile with new observations:
   - Did the user re-ask questions at a simpler level? → expertise might be lower than assumed
   - Did the user skip explanations? → preference may lean toward `direct`
   - Track correction frequency to refine style classification
   **Checkpoint:** Write the updated ProfileSnapshot atomically — never mutate in place.

6. **Persist with Guard** — Save the profile snapshot back to storage only if meaningful changes occurred (avoid unnecessary write amplification).
   **Checkpoint:** Verify persistence succeeded before considering the interaction complete. Log any failures for retry.

### Fallback and Error Routing

- **Missing profile data** → Use documented defaults (intermediate expertise, explanatory style, neutral tone) instead of halting the interaction
- **Classification inconclusive** (< 3 messages or low confidence) → Fall back to stored preference; if none stored, use conservative defaults
- **Persistence failure** → Log the failure with retry context and continue serving the user from in-memory state; schedule a background retry on the next interaction
- **Stale profile (> 90 days without updates)** → Flag as stale, re-trigger style classification on the next message burst (> 5 messages within 1 hour)
- **Corrupt or unparseable interaction history** → Discard only the corrupt entries; keep the rest and create a fresh sub-sequence

---

## Implementation Patterns

### Pattern 1: UserProfile Data Model

```python
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Dict, List, Optional


class CommunicationStyle(Enum):
    DIRECT = "direct"
    EXPLANATORY = "explanatory"
    VISUAL = "visual"
    STRUCTURED = "structured"


class ExpertiseLevel(Enum):
    BEGINNER = 1
    INTERMEDIATE = 2
    ADVANCED = 3
    EXPERT = 4


class PreferenceTone(Enum):
    FORMAL = "formal"
    CASUAL = "casual"
    NEUTRAL = "neutral"


@dataclass
class UserProfile:
    """Immutable user profile snapshot.

    All fields are immutable once created. Updates produce new instances,
    following Law 3 (Atomic Predictability) — no in-place mutation of shared state.

    Attributes:
        user_id: Unique identifier for the user
        communication_style: Detected or explicitly set communication preference
        expertise_level: Domain expertise scale for response depth tuning
        tone_preference: Preferred response formality level
        formatting_preferences: Structured formatting overrides (e.g., use_markdown_tables)
        interaction_history: Recent interactions used for style classification
        created_at: Timestamp when this profile was first created
        updated_at: Timestamp of the last profile update
    """

    user_id: str
    communication_style: CommunicationStyle = CommunicationStyle.EXPLANATORY
    expertise_level: ExpertiseLevel = ExpertiseLevel.INTERMEDIATE
    tone_preference: PreferenceTone = PreferenceTone.NEUTRAL
    formatting_preferences: Dict[str, bool] = field(default_factory=dict)
    interaction_history: List[Dict] = field(default_factory=list)
    created_at: datetime = field(
        default_factory=lambda: datetime.now(timezone.utc)
    )
    updated_at: datetime = field(
        default_factory=lambda: datetime.now(timezone.utc)
    )

    def with_updated_history(
        self,
        new_interaction: Dict,
        max_history: int = 50
    ) -> "UserProfile":
        """Return a new profile with the interaction appended and trimmed.

        Does not mutate the original instance (Law 3).

        Args:
            new_interaction: Dict containing user_message, assistant_response, timestamp
            max_history: Maximum number of interactions to retain for classification

        Returns:
            New UserProfile instance with updated history
        """
        if not new_interaction or "user_message" not in new_interaction:
            raise ValueError("interaction must contain 'user_message' key")

        trimmed = self.interaction_history[-(max_history - 1):] if len(self.interaction_history) >= max_history else []
        return UserProfile(
            user_id=self.user_id,
            communication_style=self.communication_style,
            expertise_level=self.expertise_level,
            tone_preference=self.tone_preference,
            formatting_preferences=dict(self.formatting_preferences),
            interaction_history=trimmed + [new_interaction],
            created_at=self.created_at,
            updated_at=datetime.now(timezone.utc),
        )

    def to_snapshot(self) -> Dict:
        """Serialize profile to a plain dict for storage.

        Returns:
            Flat dictionary suitable for JSON serialization or database insertion.
        """
        return {
            "user_id": self.user_id,
            "communication_style": self.communication_style.value,
            "expertise_level": self.expertise_level.value,
            "tone_preference": self.tone_preference.value,
            "formatting_preferences": dict(self.formatting_preferences),
            "interaction_history_count": len(self.interaction_history),
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }
```

### Pattern 2: Communication Style Classifier (BAD vs GOOD)

```python
# ❌ BAD: Uses string matching without normalization; fragile to casing and typos
def bad_classify_style(messages: List[str]) -> str:
    counts = {"direct": 0, "explanatory": 0}
    for msg in messages:
        if "why" in msg:
            counts["explanatory"] += 1
        elif len(msg.split()) < 10:
            counts["direct"] += 1
    return max(counts, key=counts.get)


# ✅ GOOD: Normalized token analysis with confidence scoring and explicit enum output
def classify_communication_style(
    messages: List[str],
    fallback: CommunicationStyle = CommunicationStyle.EXPLANATORY,
) -> CommunicationStyle:
    """Classify the dominant communication style from user message history.

    Analyzes word choice, sentence length patterns, and question types to
    determine whether a user prefers direct, explanatory, visual, or structured responses.

    Args:
        messages: List of recent user messages (last N messages from history)
        fallback: Style to return if classification is inconclusive

    Returns:
        Detected CommunicationStyle enum value

    Raises:
        ValueError: If messages list is empty and no fallback provided
    """
    # Law 1: Early exit for invalid input
    if not messages:
        raise ValueError("messages list must contain at least one message")

    scores: Dict[str, float] = {"direct": 0.0, "explanatory": 0.0, "visual": 0.0, "structured": 0.0}
    total_signals = 0

    for msg in messages:
        lower = msg.lower()
        words = msg.split()
        sent_len = len(words)

        # Direct signals: short sentences, imperative verbs
        if sent_len < 8:
            scores["direct"] += 1.0
            total_signals += 1

        # Explanatory signals: question words, "why", "how does"
        explanatory_markers = ["why", "how does", "explain", "tell me about", "what is"]
        if any(marker in lower for marker in explanatory_markers):
            scores["explanatory"] += 1.5
            total_signals += 1

        # Visual signals: requests for diagrams, charts, layout
        visual_markers = ["diagram", "chart", "visual", "graph", "layout", "draw"]
        if any(marker in lower for marker in visual_markers):
            scores["visual"] += 2.0
            total_signals += 1

        # Structured signals: requests for lists, tables, categories
        structured_markers = ["list", "table", "categorize", "compare", "structured"]
        if any(marker in lower for marker in structured_markers):
            scores["structured"] += 2.0
            total_signals += 1

    # If no signals detected, return fallback (Law 4: explicit default)
    if total_signals == 0:
        return fallback

    best_style = max(scores, key=scores.get)
    confidence = scores[best_style] / total_signals

    # Law 4: Fail fast — only commit if classification has reasonable confidence
    if confidence < 0.25:
        return fallback

    return CommunicationStyle(best_style)
```

### Pattern 3: Adaptive Response Generator

```python
def generate_toned_response(
    content: str,
    profile: UserProfile,
    include_code_examples: bool = True,
) -> str:
    """Generate a response tailored to the user's communication style and expertise.

    Adjusts tone, depth markers, and output formatting based on UserProfile fields.
    Follows Law 1 (Early Exit) by returning early for edge cases.

    Args:
        content: The core response text to adapt
        profile: Current user profile controlling adaptation parameters
        include_code_examples: Whether to wrap code in detailed explanations

    Returns:
        Formatted, personalized response string
    """
    # Law 1: Guard clauses at top
    if not content or not isinstance(content, str):
        raise ValueError("content must be a non-empty string")
    if not profile:
        raise ValueError("profile is required for personalization")

    parts = []

    # Tone prefix
    if profile.tone_preference == PreferenceTone.FORMAL:
        prefix = "Here is the detailed response:\n\n"
    elif profile.tone_preference == PreferenceTone.CASUAL:
        prefix = "Sure — here's what you need to know:\n\n"
    else:
        prefix = ""

    parts.append(prefix)

    # Depth adjustment based on expertise
    if profile.expertise_level == ExpertiseLevel.BEGINNER:
        # Insert definitions and step-by-step markers for beginners
        parts.append(_add_beginner_markers(content))
    elif profile.expertise_level == ExpertiseLevel.EXPERT:
        # Strip explanatory padding for experts — get to the point
        parts.append(_strip_explanations(content))
    else:
        # Intermediate / Advanced: keep standard depth
        parts.append(content)

    # Format according to communication style
    formatted = _apply_style_formatting("\n".join(parts), profile.communication_style)

    return formatted


def _add_beginner_markers(text: str) -> str:
    """Wrap technical terms in explanatory brackets for beginners."""
    # Simple heuristic: wrap known pattern names and technical identifiers
    markers = ["function", "class", "method", "property", "attribute"]
    result = text
    for marker in markers:
        result = result.replace(
            f"{marker} ",
            f"**{marker}** (a named block of logic) ",
        )
    return result


def _strip_explanations(text: str) -> str:
    """Remove verbose explanations, returning only essential technical content."""
    removal_patterns = [
        "In other words,", "To put it simply,", "Essentially,",
        "The key takeaway is that", "It's important to understand that",
    ]
    result = text
    for pattern in removal_patterns:
        if result.startswith(pattern):
            result = result[len(pattern):].lstrip()
    return result


def _apply_style_formatting(text: str, style: CommunicationStyle) -> str:
    """Apply communication-style-specific formatting to response text."""
    # Law 2: Parse at boundary — validate style enum
    if not isinstance(style, CommunicationStyle):
        raise TypeError(f"Expected CommunicationStyle, got {type(style).__name__}")

    if style == CommunicationStyle.STRUCTURED:
        # Wrap in categorized sections
        return f"[Structured]\n{text}\n[End Structured]"
    elif style == CommunicationStyle.VISUAL:
        # Add ASCII visual markers
        return f"[Visual Layout]\n{text}\n[End Visual Layout]"
    else:
        # Direct and Explanatory pass through unchanged (they control depth, not format)
        return text
```

### Pattern 4: Personalization Context Service

```python
class PersonalizationService:
    """Orchestrates user profiling, style classification, and response personalization.

    Acts as the central service that ties UserProfile management, CommunicationStyle
    classification, and AdaptiveResponse generation together into a cohesive pipeline.

    Follows Law 5 (Intentional Naming) — every method name describes its full responsibility.
    """

    def __init__(self, profile_store: Optional[Dict] = None):
        """Initialize service with optional profile storage.

        Args:
            profile_store: Mutable dict simulating a database of user profiles.
                          Keyed by user_id (str) → UserProfile.
        """
        self._store: Dict[str, UserProfile] = profile_store or {}

    def get_or_create_profile(self, user_id: str) -> UserProfile:
        """Retrieve an existing profile or create a fresh one with defaults.

        Args:
            user_id: Unique user identifier

        Returns:
            UserProfile instance (existing or newly minted with conservative defaults)
        """
        if not user_id or not isinstance(user_id, str):
            raise ValueError("user_id must be a non-empty string")

        if user_id not in self._store:
            self._store[user_id] = UserProfile(user_id=user_id)

        return self._store[user_id]

    def record_interaction_and_adapt(
        self,
        user_id: str,
        user_message: str,
        assistant_response: str,
    ) -> Dict:
        """Record a full interaction, reclassify the user's style, and adapt.

        This is the main entry point for the personalization pipeline:
        1. Load profile
        2. Record interaction into history
        3. Reclassify communication style from updated history
        4. Return adaptation results for the caller to use in response generation.

        Args:
            user_id: Unique identifier of the interacting user
            user_message: The message sent by the user
            assistant_response: The message generated by the assistant

        Returns:
            Dict with keys: profile_snapshot, detected_style, expertise_level, adaptation_applied
        """
        # Step 1: Load or create
        profile = self.get_or_create_profile(user_id)

        # Step 2: Record interaction
        new_interaction = {
            "user_message": user_message,
            "assistant_response": assistant_response,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
        updated_profile = profile.with_updated_history(new_interaction)

        # Step 3: Reclassify from full history
        message_texts = [
            hist["user_message"] for hist in updated_profile.interaction_history
        ]
        detected_style = classify_communication_style(message_texts)

        # Step 4: Build adaptation result
        return {
            "profile_snapshot": updated_profile.to_snapshot(),
            "detected_style": detected_style.value,
            "expertise_level": updated_profile.expertise_level.name,
            "adaptation_applied": True,
        }

    def get_personalized_response(
        self, user_id: str, raw_content: str
    ) -> str:
        """Generate a fully personalized response for the given user.

        Args:
            user_id: Unique identifier of the user
            raw_content: The unpersonalized response content to adapt

        Returns:
            Personalized response string matching this user's style, tone, and expertise level
        """
        profile = self.get_or_create_profile(user_id)
        return generate_toned_response(raw_content, profile)
```

---

## Constraints

### MUST DO
- Always create or load a UserProfile before attempting any personalization — never guess at preferences without data
- Apply guard clauses at the top of every method to validate required inputs (Law 1: Early Exit)
- Return new ProfileSnapshot instances from update methods instead of mutating in place (Law 3: Atomic Predictability)
- Classify communication style only after accumulating sufficient signal — minimum 3 messages before trusting the classification
- Log every profile change with timestamp, changed fields, and reason for the change
- Reference code-philosophy (5 Laws of Elegant Defense) when designing persistence logic — parse data at boundaries (Law 2), fail fast on corruption (Law 4)

### MUST NOT DO
- Hardcode a single communication style for all users — this defeats personalization entirely
- Store raw interaction messages longer than necessary for classification (>50 per user is wasteful; trim aggressively)
- Use personalization as an excuse to skip correctness — adapt tone, never alter technical accuracy
- Persist profiles on every minor update — batch changes and only write when meaningful deltas exist
- Trust unvalidated input from user-facing fields — always sanitize interaction history entries (Law 2: Parse Don't Validate)

---

## Output Template

When applying this skill, produce:

1. **Profile Summary** — Current user_id, communication_style, expertise_level, tone_preference
2. **Detected Preferences** — Style classification with confidence score and signal count
3. **Adaptation Applied** — Which personalization dimensions were adjusted (tone, depth, formatting) and how
4. **Interaction Recorded** — Confirmation that the interaction was stored and will influence future behavior
5. **Recommendations** — Suggestions for explicit user preference settings if the system is still uncertain about style or expertise

---

## Related Skills

| Skill | Purpose |
|---|---|
| `personal-workflow-framework` | Manages recurring workflows that can be personalized per user |
| `conversation-memory` | Provides the memory infrastructure for storing interaction history |
| `hierarchical-agent-memory` | Adds hierarchical memory layers for long-term preference retention across sessions |

## Live References

> Authoritative documentation links for this domain. The model follows markdown links at load time to resolve external references and inline content.

- [OpenAI User Instructions Guide](https://platform.openai.com/docs/guides/participant-design/user-instructions) — Best practices for personalizing AI assistant behavior per user
- [Anthropic System Prompt Design](https://docs.anthropic.com/en/docs/build-with-claude/system-prompts) — Techniques for structuring personalized system-level instructions
- [LangSmith User Profiles](https://docs.smith.langchain.com/cookbook/user-profiles) — Guide to managing user-specific preferences and context in LangChain applications
- [Personalized LLM Responses Research (Zhu et al.)](https://arxiv.org/abs/2310.12518) — Academic study on personalizing LLM outputs based on user characteristics
- [ReAct: Synergizing Reasoning and Acting in Language Models](https://arxiv.org/abs/2210.03629) — Foundational reasoning framework for adaptive agent behavior

