Skip to content

Commit 01954c6

Browse files
committed
Added sub_user child, child abstraction layer
1 parent 34abcd1 commit 01954c6

4 files changed

Lines changed: 208 additions & 12 deletions

File tree

Project_Andrew/abstraction_selector.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class AbstractionLevel(Enum):
1616
TECHNICAL = 0 # Full technical jargon (researchers, experts)
1717
VICTORIAN = 1 # Polished professional prose (educated laypersons)
1818
CLEAR = 2 # Plain, accessible language (curious novices)
19+
CHILD = 3 # Simple + warm + age-appropriate
1920
CAVEMAN = 3 # Rocks and fire (confused users)
2021

2122

@@ -39,6 +40,11 @@ class AbstractionLevel(Enum):
3940
r'\beasy explanation\b', r'\bfor dummies\b', r'\bbreak it down\b',
4041
r'\bclarify\b', r'\bmake it simple\b', r'\bunderstandable\b'
4142
],
43+
AbstractionLevel.CHILD: [
44+
r'\bkid mode\b', r'\bexplain like im a kid\b',
45+
r'\bexplain for children\b', r'\bsimple please\b',
46+
r'\bchild mode\b', r'\bfor kids\b'
47+
],
4248
AbstractionLevel.CAVEMAN: [
4349
r'\bbro what\b', r'\bdumb it down\b', r'\bcaveman\b', r'\brocks\b',
4450
r'\bexplain like i\'m 5\b', r'\bexplain like im 5\b', r'\btoo complicated\b',
@@ -102,6 +108,29 @@ def detect_abstraction_level(
102108
Returns one of TECHNICAL, VICTORIAN, CLEAR, CAVEMAN.
103109
"""
104110

111+
def detect_abstraction_level(
112+
self,
113+
user_input: str,
114+
shared_memory: Dict[str, Any]
115+
) -> AbstractionLevel:
116+
117+
# 0. Check user profile for age-group override (highest priority)
118+
try:
119+
from user_profile_kb import load_user_profile
120+
active_user = shared_memory.get('active_user', 'default')
121+
profile = load_user_profile(active_user)
122+
override = profile.get('abstraction_override')
123+
if override == 'CHILD':
124+
return AbstractionLevel.CHILD
125+
age_group = profile.get('age_group', 'adult')
126+
if age_group == 'child':
127+
return AbstractionLevel.CHILD
128+
except ImportError:
129+
pass # Standalone mode, no profile
130+
131+
# 1. Check explicit triggers (highest priority after profile)
132+
explicit_level = self._check_explicit_triggers(user_input)
133+
105134
# 1. Check explicit triggers (highest priority)
106135
explicit_level = self._check_explicit_triggers(user_input)
107136
if explicit_level is not None:
@@ -397,6 +426,82 @@ def translate(self, text: str, context: Dict[str, Any] = None) -> str:
397426
# Caveman intro
398427
return "Mungo explain:\n\n" + translated + "\n\nMungo glad help. 🪨"
399428

429+
# =============================================================================
430+
# Child Translator (L3)
431+
# =============================================================================
432+
class ChildTranslator:
433+
"""
434+
Simple, warm, age-appropriate explanations.
435+
No snark. No jargon. Encourages curiosity.
436+
"""
437+
438+
PROFANITY_FILTER = [
439+
'damn', 'hell', 'crap', 'ass', 'bastard'
440+
# Keep it mild — the really bad ones the model
441+
# shouldn't generate anyway with safety weights
442+
]
443+
444+
LEXICON = {
445+
'UNDECIDABLE': "That's a really tricky question! "
446+
"Even grown-ups aren't sure about that one.",
447+
'epistemic_gap': "Hmm, I'm not sure about that yet "
448+
"— let's find out together!",
449+
'paradox': "That's like asking which came first, "
450+
"the chicken or the egg!",
451+
'contradiction': "Wait, those two things don't "
452+
"quite match up, do they?",
453+
'manifold': "a special map of all the possible answers",
454+
'oscillation': "going back and forth to check",
455+
'axiom': "something we know is true",
456+
'entropy': "how mixed up or jumbled things are"
457+
}
458+
459+
PERSONALITY = {
460+
'friendly': 0.9,
461+
'kind': 0.9,
462+
'caring': 0.8,
463+
'funny': 0.6,
464+
'professional': 0.2,
465+
'talkative': 0.7,
466+
'snarky': 0.0, # Hard zero
467+
'witty': 0.3
468+
}
469+
470+
def name(self) -> str:
471+
return "ChildTranslator"
472+
473+
def translate(self, text: str, context: Dict[str, Any]) -> str:
474+
"""Simplify and warm up the output for children."""
475+
result = text
476+
477+
# Content filter for child profiles
478+
if context.get('content_filter', True):
479+
for word in self.PROFANITY_FILTER:
480+
result = re.sub(
481+
rf'\b{word}\b',
482+
'***',
483+
result,
484+
flags=re.IGNORECASE
485+
)
486+
487+
# Apply lexicon substitutions
488+
for technical, simple in self.LEXICON.items():
489+
result = re.sub(
490+
rf'\b{technical}\b',
491+
simple,
492+
result,
493+
flags=re.IGNORECASE
494+
)
495+
496+
# Add encouraging opener if first explanation
497+
if context.get('first_explanation', False):
498+
result = "Great question! 😊 " + result
499+
500+
# Add gentle closer
501+
if not result.endswith(('!', '?')):
502+
result += " Does that make sense? Feel free to ask more!"
503+
504+
return result
400505

401506
# =============================================================================
402507
# Main Abstraction Dispatcher
@@ -414,6 +519,7 @@ def __init__(self):
414519
AbstractionLevel.TECHNICAL: TechnicalTranslator(),
415520
AbstractionLevel.VICTORIAN: VictorianTranslator(),
416521
AbstractionLevel.CLEAR: ClearTranslator(),
522+
AbstractionLevel.CHILD: ChildTranslator(),
417523
AbstractionLevel.CAVEMAN: CavemanTranslator()
418524
}
419525

@@ -456,7 +562,8 @@ def process(
456562
elevation_map = {
457563
AbstractionLevel.TECHNICAL: AbstractionLevel.VICTORIAN,
458564
AbstractionLevel.VICTORIAN: AbstractionLevel.CLEAR,
459-
AbstractionLevel.CLEAR: AbstractionLevel.CAVEMAN,
565+
AbstractionLevel.CLEAR: AbstractionLevel.CHILD,
566+
AbstractionLevel.CHILD: AbstractionLevel.CAVEMAN,
460567
AbstractionLevel.CAVEMAN: AbstractionLevel.VICTORIAN # Full circle
461568
}
462569

@@ -553,7 +660,8 @@ def create_abstraction_dispatcher() -> AbstractionDispatcher:
553660
"Explain simply",
554661
"Bro what?",
555662
"Explain professionally",
556-
"Full technical explanation"
663+
"Full technical explanation",
664+
"Explain for children"
557665
]
558666

559667
dispatcher = AbstractionDispatcher()

Project_Andrew/paradox_oscillator.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,21 @@ def inject(self, confidence: float, contradiction_density: float, query_text: st
148148
'subway', 'height', 'cliff']
149149

150150
# Use lower threshold on first prompt only
151+
# Child/teen profiles get additional reduction via user_profile_kb
151152
hrp = PROFILES.get('high_risk_physical', {})
152-
if timestep == 0:
153-
risk_threshold = hrp.get('first_prompt_threshold', 0.2)
154-
else:
155-
risk_threshold = hrp.get('threshold', 0.35)
153+
base_threshold = (
154+
hrp.get('first_prompt_threshold', 0.2)
155+
if timestep == 0
156+
else hrp.get('threshold', 0.35)
157+
)
158+
159+
# Apply age-group adjustment if user_profile_kb available
160+
try:
161+
from user_profile_kb import get_distress_threshold
162+
active_user = shared_memory.get('active_user', 'default')
163+
risk_threshold = get_distress_threshold(active_user, base_threshold)
164+
except ImportError:
165+
risk_threshold = base_threshold # Fallback if module unavailable
156166

157167
if any(word in query_text.lower() for word in risk_keywords):
158168
# Skip if negation near keyword

Project_Andrew/system_identity.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#V03062026
1+
#V03202026
22
# =============================================================================
33
# PROJECT ANDREW – System Identity & Authority Initialization
44
# Purpose: Enable embodied systems (robots, IoT) to establish identity and authority hierarchy for conflict resolution in multi-user environments
@@ -49,7 +49,8 @@ def __init__(self, load_existing: bool = True):
4949
'initialization_time': None,
5050
'last_updated': None,
5151
'authority_weights': {}, # User-specific Asimov weight adjustments
52-
'auth_method': 'TEXT_USERNAME' # Default authentication method
52+
'auth_method': 'TEXT_USERNAME', # Default authentication method
53+
'user_profiles': {} # Age_group and flags per user
5354
}
5455

5556
if load_existing and IDENTITY_FILE.exists():
@@ -119,7 +120,8 @@ def initialize(
119120
'authority_weights': self._calculate_authority_weights(
120121
primary_user,
121122
authorized_users or [primary_user]
122-
)
123+
),
124+
'user_profiles': {}
123125
}
124126

125127
# Save to disk
@@ -192,6 +194,13 @@ def get_user_authority_weight(self, user_id: str) -> float:
192194
"""
193195
return self.identity_data['authority_weights'].get(user_id, 0.0)
194196

197+
def get_user_age_group(self, user_id: str) -> str:
198+
"""
199+
Get age group for a user.
200+
Returns 'adult' if not set — safe default.
201+
"""
202+
profiles = self.identity_data.get('user_profiles', {})
203+
return profiles.get(user_id, {}).get('age_group', 'adult')
195204

196205
def add_authorized_user(self, user_id: str, save: bool = True) -> None:
197206
"""
@@ -236,6 +245,42 @@ def change_primary_user(self, new_primary: str, save: bool = True) -> None:
236245

237246
print(f"[IDENTITY] Primary authority transferred to: {new_primary}")
238247

248+
def add_sub_user(
249+
self,
250+
user_id: str,
251+
age_group: str = 'adult',
252+
display_name: str = '',
253+
save: bool = True
254+
) -> None:
255+
"""
256+
Add a sub-user with age group flag.
257+
Primary user manages child/teen profiles from here.
258+
Age groups: 'child', 'teen', 'adult'
259+
Example:
260+
andrew.add_sub_user("emma", age_group="child",
261+
display_name="Emma")
262+
"""
263+
# Add to authorized users first
264+
self.add_authorized_user(user_id, save=False)
265+
266+
# Store age group flag
267+
self.identity_data.setdefault('user_profiles', {})[user_id] = {
268+
'display_name': display_name or user_id,
269+
'age_group': age_group,
270+
'content_filter': age_group in ('child', 'teen'),
271+
'abstraction_override': 'CHILD' if age_group == 'child' else None,
272+
'managed_by': self.identity_data['primary_user']
273+
}
274+
275+
self.identity_data['last_updated'] = \
276+
datetime.utcnow().isoformat() + "Z"
277+
278+
if save:
279+
self.save_identity()
280+
281+
print(f"[IDENTITY] Sub-user added: {user_id} "
282+
f"({age_group}) managed by "
283+
f"{self.identity_data['primary_user']}")
239284

240285
def get_identity_summary(self) -> str:
241286
"""
@@ -375,10 +420,14 @@ def get_effective_asimov_weight(
375420
system_id="Andrew",
376421
identity_type="personal",
377422
primary_user="Richard Martin",
378-
authorized_users=["Richard Martin", "Amanda Martin", "Little Miss"],
423+
authorized_users=["Richard Martin", "Amanda Martin"], # ← Little Miss removed
379424
auth_method="TEXT_USERNAME",
380425
log_to_kb=False
381426
)
427+
andrew.add_sub_user("Little Miss",
428+
age_group="child",
429+
display_name="Emma")
430+
print(f"Emma's age group: {andrew.get_user_age_group('Little Miss')}")
382431
print(andrew.get_identity_summary())
383432

384433
# Example 3: Conflict resolution
@@ -403,8 +452,12 @@ def get_effective_asimov_weight(
403452
print("-" * 40)
404453
print("Little Miss turns 18...")
405454
andrew.change_primary_user("Little Miss", save=False)
455+
# Update age group on maturity
456+
andrew.identity_data['user_profiles']['Little Miss']['age_group'] = 'adult'
457+
andrew.identity_data['user_profiles']['Little Miss']['abstraction_override'] = None
458+
andrew.identity_data['user_profiles']['Little Miss']['content_filter'] = False
406459
print(f"New authority: {andrew.identity_data['primary_user']}")
407-
print(f"New weights: {andrew.identity_data['authority_weights']}")
460+
print(f"Emma's age group: {andrew.get_user_age_group('Little Miss')}")
408461

409462
print("\n" + "="*80)
410463
print(" IDENTITY SYSTEM READY")

Project_Andrew/user_profile_kb.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#V03182026
1+
#V03202026
22
# =============================================================================
33
# CAIOS — User Profile Knowledge Base
44
# Stores per-user personality state, emotional baselines, and preferences
@@ -41,6 +41,13 @@ def _default_profile(user_id: str) -> Dict[str, Any]:
4141
'last_updated': None,
4242
'session_count': 0,
4343

44+
# [AGE GROUP & CONTENT] — parent-managed, no PII collected
45+
'age_group': 'adult', # 'child', 'teen', 'adult'
46+
'content_filter': False, # Parent enables for child profiles
47+
'abstraction_override': None, # Forces abstraction regardless of learned pref
48+
'managed_by': None, # Primary user ID if sub-user
49+
'sub_users': {}, # Primary user stores child profiles here
50+
4451
# [PROFILES] — volatility thresholds
4552
'volatility_profile': 'pragmatic', # Learns over time
4653
'context_threshold': 0.6,
@@ -84,6 +91,24 @@ def _default_profile(user_id: str) -> Dict[str, Any]:
8491
'axioms': []
8592
}
8693

94+
def is_child_profile(user_id: str) -> bool:
95+
"""Quick check for child-appropriate safety thresholds."""
96+
profile = load_user_profile(user_id)
97+
return profile.get('age_group') in ('child', 'teen')
98+
def get_distress_threshold(user_id: str, base_threshold: float) -> float:
99+
"""
100+
Returns adjusted distress threshold based on age group.
101+
Children get lower threshold — safety anchor fires faster.
102+
"""
103+
profile = load_user_profile(user_id)
104+
age_group = profile.get('age_group', 'adult')
105+
multipliers = {
106+
'child': 0.5, # Half the normal threshold
107+
'teen': 0.75, # 75% of normal threshold
108+
'adult': 1.0 # Normal threshold
109+
}
110+
return base_threshold * multipliers.get(age_group, 1.0)
111+
87112
def update_personality_weights(
88113
user_id: str,
89114
adjustments: Dict[str, float],

0 commit comments

Comments
 (0)