Skip to main content

User Profile System

Comprehensive user profile management for the Norns platform.


Overview

The User Profile System provides identity management, preference configuration, and behavioral insights for Norns users. It separates authentication (Zitadel) from application-level profile data.

ComponentPurpose
Profile APIBifrost API endpoints for profile CRUD
Admin UINorns Admin profile management pages
Insights PipelineAutomated behavioral pattern extraction
ContextServiceRuntime context assembly from profile data

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│ USER PROFILE SYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Norns Admin │───▶│ Bifrost API │───▶│ PostgreSQL │ │
│ │ /profile │ │ /profile/* │ │ (ravenmaskos) │ │
│ └──────────────┘ └──────────────┘ │ ├─ users │ │
│ │ │ ├─ user_channel_ids │ │
│ │ │ ├─ user_permissions │ │
│ │ │ └─ user_insights │ │
│ ┌──────────────┐ │ └──────────────────────┘ │
│ │ n8n Workflow │───────────┤ │ │
│ │ (Scheduled) │ │ │ │
│ └──────────────┘ ▼ │ │
│ ┌──────────────┐ │ │
│ │ContextService│◀──────────────┘ │
│ │ (Runtime) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

Database Schema

Core Tables

TablePurpose
usersCore identity + JSONB preferences
user_channel_identifiersMulti-channel identity (Slack, phone, etc.)
user_permissionsDomain-level access control
user_profile_insightsSystem-derived behavioral patterns

Key Columns in users

-- Identity
preferred_name VARCHAR(100)
bio TEXT
avatar_url TEXT
timezone VARCHAR(50)

-- JSONB Preferences
voice_settings JSONB -- {tts_voice_id, provider, speed, language}
response_preferences JSONB -- {verbosity, formality, humor_level, use_emoji}
context_settings JSONB -- {default_domain, work_hours, auto_switching}
notification_preferences JSONB

Channel Identifiers

Unified multi-channel identity resolution:

CREATE TABLE user_channel_identifiers (
identifier_id UUID PRIMARY KEY,
user_id UUID REFERENCES users(user_id),
identifier_type VARCHAR(50), -- 'slack', 'phone', 'telegram'
identifier_value VARCHAR(255),
is_primary BOOLEAN,
is_verified BOOLEAN,
metadata JSONB
);

Bifrost API Endpoints

Profile Management

MethodEndpointPurpose
GET/api/v1/profileFull profile
PATCH/api/v1/profileUpdate profile fields
GET/api/v1/profile/preferences/responseResponse preferences
PATCH/api/v1/profile/preferences/responseUpdate response prefs
GET/api/v1/profile/preferences/contextContext settings
PATCH/api/v1/profile/preferences/contextUpdate context settings
GET/api/v1/profile/preferences/voiceVoice settings
PATCH/api/v1/profile/preferences/voiceUpdate voice settings

Channels & Domains

MethodEndpointPurpose
GET/api/v1/profile/channelsList connected channels
POST/api/v1/profile/channelsAdd channel identifier
DELETE/api/v1/profile/channels/{id}Remove channel
GET/api/v1/profile/domainsList accessible domains
PATCH/api/v1/profile/domains/{id}/defaultSet default domain

Insights

MethodEndpointPurpose
GET/api/v1/profile/insightsGet user insights (read-only)
POST/api/v1/profile/insights/extractTrigger extraction for user
POST/api/v1/profile/insights/extract-allBatch extraction (API key)

Insights Pipeline

Automated extraction of behavioral patterns from interaction data.

Pipeline Components

n8n Workflow (every 6 hours)


Bifrost API (/insights/extract-all)


PostgreSQL Functions
├── extract_peak_hours_insight()
├── extract_domain_focus_insight()
├── extract_channel_preference_insight()
├── extract_common_intents_insight()
├── extract_activity_trend_insight()
└── extract_weekday_pattern_insight()


user_profile_insights table

Insight Types

TypeKeyDescription
usage_patternpeak_hoursHourly interaction distribution
usage_patterndomain_focusDomain usage percentages
usage_patternchannel_preferenceSlack vs web usage
usage_patternactivity_trendIncreasing/stable/decreasing
usage_patternweekday_distributionDay-of-week patterns
interaction_stylecommon_intentsTop interaction intents

Sample Insights Output

{
"peak_hours": {"1": 217, "0": 102, "21": 72},
"channel_preference": {"slack": 96.61, "web": 3.39},
"activity_trend": {
"trend": "increasing",
"avg_daily": 48.5,
"period_days": 30
}
}

n8n Workflow

  • Workflow ID: iPnizTIsYMMcrsZn
  • Name: User Insights Pipeline
  • Schedule: Every 6 hours
  • Action: POST to Bifrost API with API key

Norns Admin Profile Page

Access

Navigate to norns.ravenhelm.dev/profile

Tabs

TabPurpose
IdentityDisplay name, preferred name, avatar, bio, timezone
PreferencesResponse style, context settings, notifications
ChannelsConnected Slack, phone, email identifiers
DomainsView domain access, set default
InsightsRead-only behavioral patterns

Data Boundaries

User-Editable:

  • Core identity (name, avatar, bio, timezone)
  • Response preferences (verbosity, formality, humor)
  • Voice settings (provider, voice, speed)
  • Context settings (default domain, work hours)
  • Notification preferences
  • Channel identifiers

System-Managed (Read-Only):

  • Usage patterns and insights
  • Confidence scores
  • Derived-from counts

ContextService Integration

The ContextService uses profile data for runtime context assembly:

# context_service.py
async def _resolve_user(self, identifier: str) -> UserIdentity:
# Lookup via user_channel_identifiers table
row = await self.db.fetchrow(
"SELECT * FROM resolve_user_by_identifier($1)",
identifier
)

return UserIdentity(
response_preferences=ResponsePreferences(**row["response_preferences"]),
context_settings=ContextSettings(**row["context_settings"]),
# ... other fields
)

Context Rules Derivation

User preferences influence runtime behavior:

PreferenceEffect
verbosity: conciseSets brief_mode: true
work_hours_start/endDetermines is_work_hours
auto_domain_switchingSwitches to ravenhelm during work
default_domainUsed when no context suggests domain

Quick Commands

# Test profile API
curl -s https://bifrost-api.ravenhelm.dev/api/v1/profile \
-H "X-User-ID: 701973d2-57e4-4c84-a2ec-ded996dcf676"

# Trigger insight extraction
curl -s -X POST https://bifrost-api.ravenhelm.dev/api/v1/profile/insights/extract \
-H "X-User-ID: 701973d2-57e4-4c84-a2ec-ded996dcf676"

# View insights in database
docker exec postgres psql -U ravenhelm -d ravenmaskos -c \
"SELECT insight_type, insight_key, confidence_score
FROM user_profile_insights
WHERE user_id = '701973d2-57e4-4c84-a2ec-ded996dcf676';"

# Check n8n workflow status
curl -s https://n8n.ravenhelm.dev/api/v1/workflows/iPnizTIsYMMcrsZn \
-H "X-N8N-API-KEY: $N8N_API_KEY" | jq '.active'

Migrations

Profile system migrations in ~/ravenhelm/services/ravenmaskos-db/schema/:

MigrationPurpose
005_user_profile.sqlProfile schema extensions
006_insights_pipeline.sqlInsight extraction functions