Add user_profiles table for per-user preferences

t-535·WorkTask·
·
·
·Omni/Agent/Memory.hs
Created3 months ago·Updated1 week ago·pipeline runs →

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 (17)

🔄[system]Open → InProgress1 month ago
💬[system]1 month ago

Pipeline: dev completed (run=dev-t-535-1771511118, cost=0.0c)

🔄[system]InProgress → Open1 month ago
💬[system]1 month ago

Pipeline: verification failed: Build failed for Omni/Agent/Memory.hs (exit 1): 7[10000;10000H7[10000;10000Hthese 15 derivations will be built: /nix/store/0m5fa2krxa2d7m1rd67xnplb90yj9vbw-hs-mod-Omni_Agent_Prompt_IR.drv /nix/store/wrciq3ha3bcvsvdjjjphm4ispziykj2k-hs-mod-Omni_Agent_Trace.drv /nix/store/ppsjkiss9gjb8xvsmalw6lfbk98ajbjk-hs-mod-Omni_Agent_Op.drv /nix/store/kkmxaw0ks2y1ndih75clcsjsq4wc7dy9-hs-mod-Omni_Agent_Models.drv /nix/store/rvrifzh4ra6glx7w7p4znb7z0pgjbwi5-hs-mod-Omni_Agent_Provider.drv /nix/store/xyn2scqg0ygjhz73md9gbw8al9ragcsb-hs-mod-Omni_Agent_Prompt_Hydrate.drv /nix/store/ylwsiw8a9dr2ljc9siwn876bv7gizmnf-hs-mod-Omni_Agent_Prompt_Compile.drv /nix/store/11gi7wrxbsiq1x5g4c7cnn7lq3r4vf16-hs-mod-Omni_Agent_Interpreter_Sequential.drv /nix/store/rwzsc50issjjj8k3i0x582z269j4vv93-hs-mod-Omni_Agent_Programs_Compaction.drv /nix/store/837lb1y7w5fqpa8nfxkds9driy1z4z28-hs-mod-Omni_Agent_Programs_Agent.drv /nix/store/lgpvbrwgjmlp7d0vphgigwn6ik39klbf-hs-mod-Omni_Time.drv /nix/store/x0q0anj5xyg8pmdl2hq1cw17p6jh4qa9-hs-mod-Omni_Agent_Engine.drv /nix/store/riwas6d6ghspjx64h8qckldkaf1s89bi-hs-mod-Omni_Agent_Op_Bridge.drv /nix/store/qqa2b5rv2g8kh477i9plh601l1a1ql97-hs-mod-Omni_Agent_Memory.drv /nix/store/fjllzjh2a9rg5h0bvzmnhw1w4ba5dwfw-omni-agent-memory.drv building '/nix/store/kkmxaw0ks2y1ndih75clcsjsq4wc7dy9-hs-mod-Omni_Agent_Models.drv'... building '/nix/store/0m5fa2krxa2d7m1rd67xnplb90yj9vbw-hs-mod-Omni_Agent_Prompt_IR.drv'... building '/nix/store/wrciq3ha3bcvsvdjjjphm4ispziykj2k-hs-mod-Omni_Agent_Trace.drv'... building '/nix/store/lgpvbrwgjmlp7d0vphgigwn6ik39klbf-hs-mod-Omni_Time.drv'...

Omni/Agent/Models.hs:35:1: error: Could not find module Data.Yaml' Use -v (or :set -v in ghci) to see a list of the files searched for. | 35 | import qualified Data.Yaml as Yaml | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: builder for '/nix/store/kkmxaw0ks2y1ndih75clcsjsq4wc7dy9-hs-mod-Omni_Agent_Models.drv' failed with exit code 1; last 7 log lines: > > Omni/Agent/Models.hs:35:1: error: > Could not find module Data.Yaml' > Use -v (or :set -v in ghci) to see a list of the files searched for. > | > 35 | import qualified Data.Yaml as Yaml > | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For full logs, run: nix log /nix/store/kkmxaw0ks2y1ndih75clcsjsq4wc7dy9-hs-mod-Omni_Agent_Models.drv error: 1 dependencies of derivation '/nix/store/fjllzjh2a9rg5h0bvzmnhw1w4ba5dwfw-omni-agent-memory.drv' failed to build

[1A[1G[2K[+] Omni/Agent/Memory.hs [1A[1G[2K[0m[…] Omni/Agent/Memory.hs[0m[1B

[1A[1G[2K[+] Omni/Agent/Memory.hs [1A[1G[2K[~] Omni/Agent/Memory.hs: warning: you did not specify '--add-root'; the res...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: /nix/store/fjllzjh2a9rg5h0bvzmnhw1w4ba5dwfw-omni-a...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: these 15 derivations will be built:…[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: /nix/store/0m5fa2krxa2d7m1rd67xnplb90yj9vbw-hs-m...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: /nix/store/lgpvbrwgjmlp7d0vphgigwn6ik39klbf-hs-m...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: building '/nix/store/kkmxaw0ks2y1ndih75clcsjsq4wc7...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: building '/nix/store/0m5fa2krxa2d7m1rd67xnplb90yj9...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: building '/nix/store/wrciq3ha3bcvsvdjjjphm4ispziyk...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: building '/nix/store/lgpvbrwgjmlp7d0vphgigwn6ik39k...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: Omni/Agent/Models.hs:35:1: error: Could not fin...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: error: builder for '/nix/store/kkmxaw0ks2y1ndih75c...[1B[1A[1G[2K[~] Omni/Agent/Memory.hs: error: 1 dependencies of derivation '/nix/store/fj...[1B[0m[38;5;1m[2Kfail: bild: realise: Omni/Agent/Memory.hs [0m[0m [0m[1A[1G[2K[0m[38;5;1m[x] Omni/Agent/Memory.hs[0m[1B 1

🔄[system]Open → InProgress1 month ago
💬[human]1 month ago

Pipeline scheduler: started run=pipeline-omni-agent-memory-hs-t-535-1771561218 domain=Omni/Agent/Memory.hs

🔄[human]InProgress → Review1 month ago
💬[human]1 month ago

Pipeline scheduler: run=pipeline-omni-agent-memory-hs-t-535-1771561218 domain=Omni/Agent/Memory.hs status=done cost=13c (fund-spend=failed)

💬[human]1 week ago

Ava triage: pipeline auto-run reached status=done but the agent made NO git commits and reported blockers (missing files, path mismatches, or need clarification). This task is not actually in review — there's nothing to review. Resetting status to Open so it can be re-scoped.

🔄[human]Review → Open1 week ago
💬[human]1 week ago

ORPHAN COMMIT: coder agent produced commit 6f27cfdf27f2f580bf6201faf4dd30264e801f83 on 2026-02-19 but it was never merged into live. Reachable only via branchless reflog. Pipeline scheduler bug — see separate task. To recover: git cherry-pick 6f27cfdf27f2f580bf6201faf4dd30264e801f83 from omni/live (expect conflicts after 6+ weeks of drift). Otherwise re-implement from scratch.