Family Multi-User Mode
One Riverse deployment, multiple family members — each with isolated chat history, profile, memory, finance/health data, all sharing the same Postgres database without crossing wires.
Concepts
| Term | What it is |
|---|---|
| Account | A row in the accounts table. Has an integer id, an internal name (e.g. jk, wife), and a display_name. |
| Owner | Synonym for account. Every business row (observation, profile fact, memory snapshot, finance transaction…) carries owner_id to scope it. |
| Admin | The account with id=1. Created automatically at first init. Only the admin can invite new members, sign devices out, or change family settings. |
| Device | A row in access_tokens. One per browser session. The token plaintext only ever lives in the browser cookie — the DB stores SHA-256 hash + the first 8 chars for display. |
| Invite | A single-use UUID URL admin shares with a family member. Opening it on the family member's device mints a fresh device token automatically. |
When you need it
You don't need this if:
- You're the only person using Riverse, period.
You do want it when:
- Your spouse / kid / parent want their own chat with their own memory, not mixed into yours.
- You're running on a single VPS and want each family member to talk to the agent from their own phone.
- You want per-person LLM token accounting.
Set it up
1. Initialise the database
The admin account is seeded automatically the first time setup_db.py runs. Its name comes from settings.yaml if present, otherwise from the OS user (whoami), with jk as a final fallback.
# settings.yaml — optional, customise the seed admin name
admin_name: "alice"
admin_display_name: "Alice"
Re-running is idempotent — existing accounts are kept (ON CONFLICT DO NOTHING).
2. Add a family member
Use the System page in the JKRiver web UI: System → 家庭成员 → + Add member.
- Internal name: a unique slug like
wifeorkid1. This is what--owner-namematches. - Display name: shown in the UI, can contain spaces / CJK.
Or insert directly via SQL:
3. Invite their device
In System → 家庭成员 → 邀请新设备:
- Pick which family member the invite is for.
- Optionally add a label ("Wife iPhone", "Living-room iPad").
- Click Generate — you get a one-time URL plus a QR code.
- Share the URL (or have them scan the QR).
- On their device, they name the device and click Accept → cookie is set → they're logged in to their account.
The invite is single-use and expires in 24 hours.
4. Approval gate (optional)
For tighter control, require admin approval before a newly accepted invite is allowed in. Set in settings.yaml:
With this on, accepting an invite parks the new device in pending_approval=true. The family member sees a "waiting for admin to approve" page. You then approve them in the System UI's Pending devices banner.
5. IM mappings (Telegram / Discord)
If your spouse uses your Telegram bot instead of the web UI, map their Telegram user_id to their account in System → 家庭成员 → + 添加 IM 映射. Their incoming messages get automatically tagged with their owner_id, not yours.
The mapping is stored in channel_identities(channel, external_id, owner_id). The bot consults this table on each message (with a short cache) so changes take effect within a minute.
RiverHistory: process history into one specific account
When the same DB is shared with RiverHistory for batch importing exported ChatGPT/Claude/Gemini histories, RH writes everything under one chosen owner per run:
python3 run.py chatgpt 50 # auto-pick owner (only works if 1 account exists)
python3 run.py chatgpt 50 --owner-name jk # write under 'jk'
python3 run.py claude max --owner-name wife # write under 'wife'
If you don't pass --owner-name in a multi-account DB, run.py lists the available accounts and asks you to specify one.
How isolation actually works
Every business table has an owner_id column (default 1). All writes during a session carry the requesting user's owner_id; all reads filter WHERE owner_id = ?. The columns include:
observations,user_profile,current_profile,user_modelraw_conversations,conversation_turns,session_meta,session_tags,session_summariesmemory_snapshot,memory_clusters,memory_embeddings,fact_edgesstrategies,trajectory_summary,relationships,review_logoutsource_tasks,proactive_log,token_usagefinance_transactions,withings_*
The sleep pipeline now loops per owner rather than processing one global pool, so each family member gets their own profile/memory extraction without cross-contamination.
Audit log
Every membership / device / invite / IM-mapping change is recorded in family_audit. Visible in the System UI under Activity, with pagination.
Limitations
- The same browser cookie can hold only one device token — so two family members can't both be "logged in" on the same browser at the same time. They'd use separate browsers or profiles.
- IM bots route by
channel_identitiesmapping; without a mapping, unknown users hit thetelegram.allowed_user_idswhitelist or get denied. - All accounts share the same LLM backend config (model, API key, cloud-fallback settings). Per-account LLM keys are not (yet) supported.