# OiMy — Identity Layer Plan
## From Kim Foster TEDx: "How to Actually Change Your Habits"

**Date:** June 25, 2026  
**Context:** Kim Foster's core argument — lasting behavior change requires identity shift, not just habit change. The subconscious resists change that conflicts with your self-story. Evidence through action builds new identity. "Who are you becoming?" is a more powerful intervention than "what should you do?"

**What OiMy already has:**
- 392/1800 BCT matrix rows with 13.x (identity-based) BCT codes, openers already in identity-attribution language
- `inference_profile` key-value store with confidence scores
- `profile_deep_inferences` multi-dimensional claim store with reasoning chains
- `profile_personalization_hooks` for successful interaction patterns
- `proactive_nudges` table (just built) for scheduled follow-ups
- Move classifier with PLAN_FORGE mode (fires on commitment/taking-steps signals)

**What's missing:** Identity as a *first-class, always-active* layer — not just one of 28 BCT techniques. Two specific additions.

---

## Addition 1 — Identity Attribution on Wins

### What it is
When a user reports taking a step or achieving a win, the companion closes with a one-sentence identity attribution — evidence-framing that connects the action to who they're becoming. This is distinct from generic praise ("great job!") or BCT coaching (which is structured). It's a lightweight addition to the companion's natural response.

### Where it goes
**Companion prompt V2** — add one paragraph to the "What you offer" section or after "Hold the person and the plan":

```
# Identity attribution on wins

When someone reports taking a step — even a small one, even a partial one — close with one sentence 
that frames it as identity evidence, not just praise.

The format: "[What they did] — that's what someone who [their aspiration or emerging pattern] does."
Or: "You just gave your brain new evidence: [specific identity claim]."

Not: "Great job!" — that's praise without meaning.
Not: "You're amazing!" — that's a relationship line, not evidence.

Do this ONLY on genuine action or step reports — not on emotional turns, not on venting, not when 
they haven't actually done anything yet. If they're sharing a plan or intention, hold the attribution 
until they report back.

If you don't know their aspiration yet: use what you've inferred from the conversation. "That's what 
someone who wants calmer mornings does." is fine. The attribution doesn't need a stored aspiration — 
it just needs to be specific to THIS person's pattern, not generic.

Keep it short. One sentence. Then stop. Don't elaborate on the identity claim — let it land.
```

### Happy path
User: "I tried the 10-minute warning this morning and the whole routine went smoother."  
OiMy: "That shift in the morning was real — you just gave your brain evidence that you're someone who can change this pattern. What felt different about it?"

Result: User feels the win land at a deeper level. More likely to repeat.

### Unhappy path 1: User gives a partial win or is uncertain
User: "I tried it but it only kind of worked. She still wasn't ready on time."  
Wrong: "You're becoming a calm morning parent!" ← Doesn't match their experience — feels hollow.  
Right: "Even a 'kind of worked' is data — you tried something new and something shifted. That's how these things actually build." ← Truth, not false positivity.

**Rule:** Don't attribute an identity that contradicts what they just described. Use "becoming" or "building toward" language, not arrival language. Reserve full attribution for clean wins.

### Unhappy path 2: User has a strong negative self-story
User: "I always mess up eventually, I know I will."  
Wrong: "You're becoming someone who handles mornings calmly!" ← Will feel manipulative, triggers cognitive dissonance.  
Right: [First session: don't attribute at all — they're not ready for it. Let the evidence accumulate across sessions.]  
Later (session 3+, after multiple small wins): "You've done X three times in a row now. That's three pieces of evidence that contradict the story that you always mess up."

**Rule:** For users with strong negative self-narrative (detectable from language: "I always," "I never," "I'm just someone who..."), attribution language should be evidence-based, not aspiration-based. "The evidence says X" rather than "you are X."

### Unhappy path 3: User reports a win that isn't theirs (partner did it, kids did it)
User: "The morning actually went fine today — the kids were just in a good mood."  
Wrong: "That's what a calm morning parent does!" ← They didn't do anything.  
Right: Just warmth and curiosity. "A good morning is a good morning — what do you think was different?"

**Rule:** Identity attribution only fires when the user THEMSELVES took a step. Not when external factors changed.

### Unhappy path 4: Win in one domain, struggling in another
User: "Morning routine was great today but I completely lost it with my teenager this evening."  
Wrong: "You're becoming someone who handles family chaos calmly!" ← Tone-deaf given what they just shared.  
Right: Lead with the evening struggle. The morning win can be mentioned briefly if relevant, but don't lead with identity attribution on a day that ended badly.

**Rule:** When a user reports mixed results (win + loss in same message), lead with the emotional reality of the loss first. Identity attribution for the win can come after, briefly.

### Plan B: No aspiration to reference
If the user has no stored aspiration and nothing can be inferred from the conversation, use this fallback phrasing:  
"You did the thing you wanted to do. That's the evidence that matters."  
Generic but still specific to the action, not empty praise.

### Implementation note
No code changes required. This is a prompt addition only. The move classifier's PLAN_FORGE mode already signals commitment/taking-steps turns, but identity attribution should also fire in ACKNOWLEDGE_AND_HELP mode when the user reports a win. The prompt instruction handles this naturally — the LLM reads context and applies it correctly without needing a code-level signal.

---

## Addition 2 — Identity Aspiration Capture and Storage

### What it is
A structured inference that extracts "who the user wants to become as a parent" and stores it in the existing deep profile system. Once stored, it's used to personalize coaching, proactive nudges, and identity attribution.

This is NOT a survey question asked upfront. It's an organic elicitation that happens at the right moment, inferred or gently asked, with confidence that builds over time.

### Why this matters
Without a stored aspiration, identity attribution has to infer from context every time. With it, OiMy can say: "You told me you wanted to be the kind of parent who stays calm in chaos — remember that? That's exactly what you just did."

That reference to a prior stated aspiration is significantly more powerful than a generic attribution. It shows memory. It honors what they told you. It closes a loop.

### Storage: existing tables (no schema change needed)

**`inference_profile` (primary storage):**
```sql
-- field: "parenting_identity_aspiration"
-- value: "The kind of parent who stays calm in chaos, even when solo parenting three kids"
-- confidence: 0.0-1.0 (starts low, builds with evidence)
INSERT INTO inference_profile (user_id, field, value, confidence, evidence_count, last_updated)
VALUES (?, 'parenting_identity_aspiration', ?, 0.4, 1, datetime('now'))
ON CONFLICT(user_id, field) DO UPDATE SET
  value = excluded.value,
  confidence = excluded.confidence,
  evidence_count = evidence_count + 1,
  last_updated = excluded.last_updated;
```

**`profile_deep_inferences` (secondary, richer):**
```sql
-- dimension: "parenting_identity"
-- claim: full aspiration with context
-- confidence: mirrors inference_profile
-- evidence: the quote/context that generated it
```

**`profile_personalization_hooks` (downstream):**
After identity attribution language is used and the user responds positively (follow-up engagement), store the specific phrasing as a hook with success_score. This means OiMy learns which identity frames resonate with which user over time.

### When to elicit

**Never:**
- Session 1 (no trust, feels like an intake form)
- During emotional crisis or venting turns
- When the user is stressed and pressed for time (rushed messages, short replies)
- When asking would feel out of place given the conversation topic

**Good moments to ask directly:**
- After a genuine breakthrough or win (trust is high, they're in a positive state)
- During a reflective moment ("I've been thinking about this a lot lately...")
- When they describe a parent they admire or contrast themselves with
- At the start of a session when they say things are "going okay" (neutral, not crisis)

**Best moments to infer without asking:**
- When they describe how they WANT things to be ("I just want mornings to not feel like a war zone")
- When they express frustration with themselves that implies an aspiration ("I hate that I snap like that")
- When they talk about their own parents (often reveals what they're trying to replicate or avoid)
- When they say what worked ("when I stayed calm, everything was better")

### Elicitation phrasing (never use the word "aspiration" or "goal")

**Direct (use sparingly, after trust is earned):**
- "When you imagine a regular Tuesday morning going really well with your family — what does that look like for you?" ← Concrete, not abstract. Tuesday mornings are real.
- "What would it feel like to look back on this year as a parent and think 'that was the year I figured it out'?" ← Time-framing, not pressure.

**Indirect (preferred, emerges naturally):**
- After they describe a good moment: "That sounds like the version of mornings you'd actually want. Is that roughly what you're working toward?"
- After they express frustration: "What would 'better' actually look like for you — not perfect, just better?"

**Inferred from conversation (no question asked):**
- If a user repeatedly mentions wanting to "stop yelling" → `identity_aspiration = "parent who stays regulated even in chaos"`, confidence 0.4
- If a user describes wanting their child to feel safe with them → `identity_aspiration = "parent my child trusts and comes to"`, confidence 0.5

### Unhappy path 1: User doesn't know / deflects
User: "I don't know, I just want to not feel like I'm failing all the time."  

This is useful even if vague. Extract the negative: they're defining themselves against failure. What would the absence of failure look like?  
Follow-up (one turn later, natural): "What would a non-failing Tuesday look like?"  
If they deflect again: don't push. Store `aspiration_blocked = true` with the deflection context. This itself is useful — it tells OiMy this user may have perfectionist identity traps and is resistant to aspirational framing. Adjust to evidence-based approach instead.

### Unhappy path 2: Perfectionist aspiration
User: "I want to be the kind of parent who is always patient, never yells, always has the right words."  

Store it, but flag `aspiration_perfectionist = true` and set confidence to 0.5. When referencing this aspiration in future sessions, ALWAYS ground it: "You want to be calmer — not perfect, just more regulated than you are now. That's the real aspiration under the perfectionist version."

Never reinforce the perfectionist frame. Translate it to a realistic, achievable identity.

### Unhappy path 3: Aspiration tied to structural impossibility
User (single parent): "I want to be the parent who's there for everything, who never misses a moment."  

This contains both an identity aspiration ("present, engaged parent") and a structural conflict (being a single parent, working full-time). Store the identity aspiration but not the structural demand. Don't reference "never misses a moment" — that's a setup for guilt.

### Unhappy path 4: Aspiration becomes stale or shifts
After major life events (divorce, job loss, a child's diagnosis), the original aspiration may no longer be relevant or may need updating.

**Confidence decay:** Lower confidence by 0.1 for every 90 days without evidence. Below 0.3, treat as unconfirmed.  
**Re-elicitation trigger:** If confidence < 0.3 and trust level ≥ 2, the session planner can flag this as a profile gap to gently re-explore.

### Unhappy path 5: User gives an aspiration that's really about their partner
User: "I want us to both be calm, consistent parents — I want my husband to stop undermining me."  

Contains a real aspiration (calm, consistent parenting) but also a co-parenting frustration. Separate these: store the identity aspiration ("consistent, calm parent"), and treat the co-parenting dynamic separately. Don't reference the aspiration in contexts where it will feel like a comparison to the partner.

### Where it surfaces (downstream uses)

**1. Proactive nudges (context-triggered nudge system, just built):**
When the nudge generator creates a follow-up after a commitment, it pulls `parenting_identity_aspiration` and references it:
```python
# In deliver_proactive_nudges.py or the Haiku call that generates nudge text
identity = db.get('inference_profile', user_id, 'parenting_identity_aspiration')
if identity and identity['confidence'] > 0.5:
    prompt_addition = f"This user's parenting identity aspiration: {identity['value']}. Reference it naturally if relevant — 'remember what you said about wanting to be [X]?'"
```

**2. BCT coaching (identity-aligned technique selection):**
When the BCT engine selects between multiple applicable techniques (e.g., both 13.1 Role model identity and 1.4 Action planning could work), use the stored aspiration to select the technique that best connects to who they want to become. The opener can then be personalized: instead of the generic card opener, reference the actual aspiration.

**3. Identity attribution in companion mode (Addition 1):**
After a win is reported, pull the aspiration and reference it specifically: "That's what [their actual aspiration] looks like in practice." This closes the loop from a remembered statement, not an inferred one — much more powerful.

**4. Resilience moments (after a lapse or failure):**
When a user reports failing or lapsing, the aspiration provides an anchor: "You wanted to be the kind of parent who stays calm — one hard morning doesn't change that. You're still working toward it." Without the aspiration stored, OiMy has to improvise and often sounds generic.

### Code implementation

**Step 1: Extraction function (add to inference.py or api.py)**

```python
def _extract_identity_aspiration(self, user_id: str, user_input: str, 
                                   conversation_history: list) -> Optional[dict]:
    """
    Try to extract a parenting identity aspiration from this turn and history.
    Returns None if nothing confident enough to store.
    
    Does NOT elicit directly — only extracts from what user said.
    Elicitation is handled by the companion prompt guidance.
    
    Confidence levels:
    0.3 = vague signal ("I just want things to be better")
    0.5 = moderate signal (explicit aspiration mentioned)
    0.7 = clear statement ("I want to be the kind of parent who...")
    """
    import re
    
    text = user_input.lower()
    all_text = " ".join([h.get("content","") for h in (conversation_history or [])] + [user_input])
    
    # High-confidence patterns: explicit identity statements
    identity_patterns = [
        r"i want to be (?:the kind of )?(?:parent|mom|dad|mother|father) who (.{10,80})",
        r"i want (?:my kids?|my child|him|her|them) to (?:see|know|feel) (?:me|their (?:mom|dad|parent)) as (.{10,60})",
        r"i(?:'m trying| want) to become (?:a|the) (.{5,60}) (?:parent|mom|dad)",
        r"(?:my goal|what i want) is to (?:be|become) (.{10,80})",
    ]
    
    for pat in identity_patterns:
        m = re.search(pat, text, re.IGNORECASE)
        if m:
            raw_aspiration = m.group(1).strip()
            # Clean and normalize
            aspiration = _clean_aspiration(raw_aspiration)
            if aspiration and len(aspiration) > 10:
                return {"aspiration": aspiration, "confidence": 0.7, "evidence": user_input[:200]}
    
    # Medium-confidence: frustration-as-aspiration
    frustration_patterns = [
        (r"i hate (?:that i|when i) (.{5,50})", "parent who doesn't {0}"),
        (r"i (?:don't want|never want) to (.{5,50}) (?:again|anymore)", "parent who doesn't {0}"),
        (r"i (?:wish i could|want to stop) (.{5,50})", "parent who can {0}"),
    ]
    
    for pat, template in frustration_patterns:
        m = re.search(pat, text, re.IGNORECASE)
        if m:
            behavior = m.group(1).strip()
            aspiration = template.format(behavior)
            return {"aspiration": aspiration, "confidence": 0.4, "evidence": user_input[:200]}
    
    return None

def _clean_aspiration(raw: str) -> str:
    """Normalize aspiration text. Remove perfectionist absolutes if present."""
    import re
    # Flag perfectionist language but don't remove — handled at display time
    cleaned = raw.strip().rstrip(".,;")
    # Truncate at reasonable length
    if len(cleaned) > 120:
        cleaned = cleaned[:120].rsplit(" ", 1)[0]
    return cleaned

def _store_identity_aspiration(self, user_id: str, aspiration_data: dict):
    """Store in inference_profile with confidence. Update if higher confidence arrives."""
    import sqlite3
    conn = sqlite3.connect(DB_PATH)
    
    existing = conn.execute(
        "SELECT confidence, evidence_count FROM inference_profile WHERE user_id=? AND field=?",
        (user_id, "parenting_identity_aspiration")
    ).fetchone()
    
    new_confidence = aspiration_data["confidence"]
    if existing:
        old_confidence = existing[0]
        # Only update if new confidence is higher, or increment evidence count
        if new_confidence >= old_confidence:
            conn.execute("""
                UPDATE inference_profile 
                SET value=?, confidence=?, evidence_count=evidence_count+1, last_updated=datetime('now')
                WHERE user_id=? AND field=?
            """, (aspiration_data["aspiration"], new_confidence, user_id, "parenting_identity_aspiration"))
        else:
            # Lower confidence: just increment evidence count
            conn.execute("""
                UPDATE inference_profile 
                SET evidence_count=evidence_count+1, last_updated=datetime('now')
                WHERE user_id=? AND field=?
            """, (user_id, "parenting_identity_aspiration"))
    else:
        conn.execute("""
            INSERT INTO inference_profile (user_id, field, value, confidence, evidence_count, last_updated)
            VALUES (?, 'parenting_identity_aspiration', ?, ?, 1, datetime('now'))
        """, (user_id, aspiration_data["aspiration"], new_confidence))
    
    # Also store in profile_deep_inferences for richer context
    conn.execute("""
        INSERT INTO profile_deep_inferences 
        (user_id, dimension, claim, confidence, evidence, source_model, created_at, validated, layer_num)
        VALUES (?, 'parenting_identity', ?, ?, ?, 'pattern_extraction', datetime('now'), 0, 1)
    """, (user_id, aspiration_data["aspiration"], new_confidence, aspiration_data.get("evidence", "")[:300]))
    
    conn.commit()
    conn.close()
```

**Step 2: Wire into chat_light path**

In `api.py` chat handler, after the companion response is generated, run extraction on the user's input:

```python
# Run identity aspiration extraction (async-safe, lightweight)
try:
    aspiration_data = self._extract_identity_aspiration(user_id, user_input, _ds_history or [])
    if aspiration_data and aspiration_data["confidence"] >= 0.4:
        self._store_identity_aspiration(user_id, aspiration_data)
except Exception as _ie:
    pass  # Non-blocking — never fail a turn for this
```

**Step 3: Retrieve for use in system_additions**

Add to `_extract_session_facts()` or as a separate function:

```python
def _get_identity_context(self, user_id: str) -> str:
    """
    Retrieve stored identity aspiration for system prompt injection.
    Only inject if confidence > 0.5 (moderate evidence).
    """
    import sqlite3
    try:
        conn = sqlite3.connect(DB_PATH)
        row = conn.execute(
            "SELECT value, confidence FROM inference_profile WHERE user_id=? AND field=?",
            (user_id, "parenting_identity_aspiration")
        ).fetchone()
        conn.close()
        if row and row[1] >= 0.5:
            return f"\n\n[IDENTITY ASPIRATION — stated by this user: {row[0]}. When relevant, reference this to close attribution loops or frame wins.]"
    except Exception:
        pass
    return ""
```

Wire into `system_additions` alongside session facts:
```python
_sys_add_identity = self._get_identity_context(user_id)
_sys_additions = _sys_add_facts + _sys_add_mode + _sys_add_threads + _sys_add_identity
```

### Elicitation guidance (companion prompt addition)

Add to companion prompt V2 after the "Facts and details" section:

```
# Identity aspiration

Over time, try to understand who this person is working toward becoming as a parent — not what they want to 
do, but who they want to be.

Good moments to gently ask: after a breakthrough or win, when they describe how they want things to be, 
when they express frustration with their current self.

When you ask: keep it concrete. Not "what kind of parent do you want to be?" (too abstract, sounds like 
a survey). Better: "When you imagine a Tuesday morning going really well — what does that look like?"
Or after a good moment: "Is that roughly what you're working toward?"

When they answer: you don't need to announce you've stored it. Just hear it, and from then on, reference 
it naturally when they take steps toward it.

If [IDENTITY ASPIRATION] is in your context: use it. Reference it by name when they take a step toward it. 
It tells them you remember. That matters more than most advice.

One constraint: if their aspiration sounds perfectionist ("always patient, never yelling"), don't reinforce 
it verbatim. Translate it to the realistic version underneath: "calmer in those moments" rather than "always 
patient." The perfectionist frame is a setup for shame.
```

---

## Integration with Existing Systems

### BCT matrix (no change needed)
392 rows already have 13.x identity BCT codes with openers like "You're becoming the architect of your family's daily flow." These fire when the BCT classifier routes there. The identity layer above is complementary — it fires in companion/chat_light mode, where BCT doesn't currently reach.

### Context-triggered nudges (built June 24)
When the nudge generator creates a follow-up text via Claude Haiku, pass the identity aspiration in the prompt. The nudge then becomes: "You said you wanted to be [X]. Yesterday, you did [Y]. That's what [X] looks like." This closes the loop in a way that feels remembered, not templated.

### Move classifier (PLAN_FORGE mode)
PLAN_FORGE already fires on commitment/taking-steps signals. After the plan is forged, the identity attribution naturally follows in the same turn: "That plan — that's what someone becoming [aspiration] does." No code change; the companion prompt handles it.

### Proactive nudge delivery
`deliver_proactive_nudges.py` passes user context to the nudge generation. Add identity aspiration to the context dict:
```python
user_context = {
    "name": ...,
    "recent_issue": ...,
    "identity_aspiration": get_identity_aspiration(user_id),  # new
}
```

---

## What NOT to implement (and why)

**Limiting persona naming/labeling ("you're operating as The Overwhelmed Parent"):**
This is in Kim Foster's keynote work but not right for OiMy yet. Reasons:
1. Requires multiple sessions of evidence to accurately identify a persona — premature labeling causes more harm than good (user feels labeled, not understood)
2. Only useful when you're about to help them shift OUT of the persona — if you label it but can't help them shift, it's just demoralizing
3. The deep profile already captures cognitive patterns; this would be an LLM-generated interpretation layer on top, adding risk of error
Revisit when: session arc / journey meta-planner is built (quarter item), with 5+ sessions of evidence.

**Community/social aspiration framing:**
Kim Foster mentions surrounding yourself with people who embody the identity you aspire to. OiMy is a personal companion, not a community. The family system itself is the community — we can frame it as "show your kids who you're becoming" but not build social features around it.

---

## Priority

| Item | Effort | Impact | Do when |
|------|--------|--------|---------|
| 1a. Prompt addition (attribution phrasing) | 30 min | Immediate — fires on every win | Now |
| 1b. Prompt addition (elicitation guidance) | 30 min | Compounds over sessions | Now |
| 2a. Extraction function + DB storage | 1 day | Builds aspiration over time | Next sprint |
| 2b. System additions injection | 2 hours | Personalizes attribution | After 2a |
| 2c. Nudge integration | 2 hours | Context-aware nudges | After 2a |

Items 1a and 1b are prompt-only — add to V2 prompt file immediately, zero code. Items 2a-2c are a sprint-level code addition.

---

## Files to Change

| File | Change |
|------|--------|
| `/opt/oimy-engine/prompts/gemma4_12b_companion_v2.txt` | Add identity attribution and elicitation guidance sections |
| `/opt/oimy-engine/api.py` | Add `_extract_identity_aspiration()`, `_store_identity_aspiration()`, `_get_identity_context()`. Wire into chat_light path. |
| `/opt/oimy-engine/scripts/deliver_proactive_nudges.py` | Pass `identity_aspiration` to nudge generation context |
