Add user_profiles table for per-user preferences

t-535·WorkTask·
·
·
Created3 weeks ago·Updated3 weeks ago

Description

Edit

Add user_profiles table to Memory.hs for per-user profile data.

Background

Ava is designed to be multi-tenant (multiple users via Telegram). Currently:

  • users table exists with: id, telegram_id, email, name, created_at
  • settings table is GLOBAL (key/value, no user_id)
  • User preferences are scattered or stored in global settings

We need a proper per-user profile system for:

  • Name, pronouns, timezone
  • Communication preferences (tone, verbosity)
  • Notes/context the agent learns over time
  • Any user-specific configuration

Current Schema

-- Existing users table
CREATE TABLE users (
  id TEXT PRIMARY KEY,           -- UUID
  telegram_id INTEGER UNIQUE,    -- Telegram user ID
  email TEXT,
  name TEXT NOT NULL,
  created_at TIMESTAMP
);

-- Existing settings table (GLOBAL, no user scope)
CREATE TABLE settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

Proposed Changes

Option A: New user_profiles table (Recommended)

CREATE TABLE IF NOT EXISTS user_profiles (
  user_id TEXT PRIMARY KEY REFERENCES users(id),
  display_name TEXT,              -- What to call them
  pronouns TEXT,                  -- he/him, she/her, they/them, etc.
  timezone TEXT,                  -- America/New_York, etc.
  tone_preference TEXT,           -- terse, detailed, casual, formal
  notes TEXT,                     -- Free-form notes about the user
  updated_at TIMESTAMP NOT NULL,
  
  -- Could add more structured fields:
  -- locale TEXT,                 -- en-US, etc.
  -- notification_hours TEXT,     -- JSON: {"start": "09:00", "end": "22:00"}
);

Option B: Scope existing settings table by user

-- Modify settings to include optional user_id
CREATE TABLE settings (
  key TEXT NOT NULL,
  user_id TEXT REFERENCES users(id),  -- NULL = global setting
  value TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  PRIMARY KEY (key, user_id)
);

-- Then store as:
-- key: "profile.timezone", user_id: "uuid-123", value: "America/New_York"
-- key: "profile.pronouns", user_id: "uuid-123", value: "he/him"

Recommendation: Option A is cleaner for profile data. Option B is better if we want flexible per-user settings beyond just profile. Could do both.

Implementation

1. Add schema (Memory.hs ~line 1120)

Add after settings table creation:

-- User profiles table for per-user preferences
SQL.execute_
  conn
  "CREATE TABLE IF NOT EXISTS user_profiles (\
  \  user_id TEXT PRIMARY KEY REFERENCES users(id),\
  \  display_name TEXT,\
  \  pronouns TEXT,\
  \  timezone TEXT DEFAULT 'America/New_York',\
  \  tone_preference TEXT DEFAULT 'terse',\
  \  notes TEXT,\
  \  updated_at TIMESTAMP NOT NULL\
  \)"

2. Add UserProfile type (Memory.hs)

data UserProfile = UserProfile
  { profileUserId :: Text
  , profileDisplayName :: Maybe Text
  , profilePronouns :: Maybe Text
  , profileTimezone :: Text
  , profileTonePreference :: Text
  , profileNotes :: Maybe Text
  , profileUpdatedAt :: UTCTime
  }
  deriving (Show, Eq, Generic)

instance Aeson.ToJSON UserProfile
instance Aeson.FromJSON UserProfile
instance SQL.FromRow UserProfile
instance SQL.ToRow UserProfile

3. Add CRUD functions (Memory.hs)

-- | Get user profile, creating default if not exists
getUserProfile :: UserId -> IO UserProfile

-- | Update user profile (upsert)
updateUserProfile :: UserProfile -> IO ()

-- | Update a single profile field
setProfileField :: UserId -> Text -> Text -> IO ()
-- e.g., setProfileField uid "timezone" "America/Los_Angeles"

-- | Get profile field
getProfileField :: UserId -> Text -> IO (Maybe Text)

4. Add tools for agent to update profile (Memory.hs or Tools/)

-- Tool: update_user_profile
-- Allows agent to update profile based on learned info
updateUserProfileTool :: UserId -> Engine.Tool

-- Tool schema:
-- { "field": "timezone", "value": "America/Los_Angeles" }
-- { "field": "notes", "value": "Prefers morning meetings, works on Omni" }

5. Integrate into prompt (Bot.hs)

In the systemPrompt construction (~line 1555), add:

-- Load user profile
profile <- Memory.getUserProfile (Memory.UserId (Memory.unUserId uid))

let userSection = 
      "\n\n## User Profile\n"
      <> "Name: " <> fromMaybe userName (profileDisplayName profile) <> "\n"
      <> maybe "" (\p -> "Pronouns: " <> p <> "\n") (profilePronouns profile)
      <> "Timezone: " <> profileTimezone profile <> "\n"
      <> maybe "" (\n -> "Notes: " <> n <> "\n") (profileNotes profile)

6. Migration for existing users

On first access, create default profile from users table:

getUserProfile uid = do
  existing <- queryProfile uid
  case existing of
    Just p -> pure p
    Nothing -> do
      user <- getUser uid
      let defaultProfile = UserProfile
            { profileUserId = unUserId uid
            , profileDisplayName = Just (userName user)
            , profilePronouns = Nothing
            , profileTimezone = "America/New_York"
            , profileTonePreference = "terse"
            , profileNotes = Nothing
            , profileUpdatedAt = now
            }
      insertProfile defaultProfile
      pure defaultProfile

Files to Modify

1. Omni/Agent/Memory.hs

  • Add UserProfile type and instances
  • Add table creation in initSchema
  • Add CRUD functions
  • Add tool definitions
  • Export new functions

2. Omni/Ava/Telegram/Bot.hs

  • Load profile in handleMessage
  • Add to systemPrompt construction
  • Register update_user_profile tool

Testing

1. typecheck.sh Omni/Agent/Memory.hs 2. Add unit tests for UserProfile CRUD 3. Test profile appears in prompt 4. Test agent can update profile via tool

Future Considerations

  • Profile could include notification preferences (quiet hours)
  • Could add profile versioning/history
  • Could sync profile across channels (if user on Telegram + Discord)
  • Could add profile import/export

Timeline (0)

No activity yet.