Principle:OpenHands OpenHands User Migration
| Knowledge Sources | |
|---|---|
| Domains | Organization_Management, Multi_Tenancy |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Migrating legacy user data to a new organizational structure transforms user records from a flat model to an org-based model while preserving billing state and service continuity.
Description
The User Migration principle implements a data migration pattern that transitions existing user records from a legacy flat structure (where users exist independently) to a multi-tenant organizational structure (where users belong to organizations). This migration must:
- Create a personal organization for the user if one does not already exist.
- Transfer LiteLLM proxy entries (teams, keys, model access) from the user's legacy configuration to the new organization's scope.
- Migrate billing state (Stripe customer records, subscription data) from the user-level to the organization-level.
- Preserve all existing functionality so the user experiences no service disruption.
This pattern is critical during the transition period when a system evolves from single-tenant to multi-tenant architecture. It ensures that existing users are seamlessly onboarded into the new organizational model without requiring manual re-registration or data re-entry.
Usage
Apply this principle when evolving a system's data model in a way that requires existing records to be restructured. Common scenarios include:
- Migrating individual user accounts into organizational hierarchies
- Transitioning from flat billing to org-level billing
- Moving from per-user API keys to per-organization API keys
- Any backward-compatible schema evolution that must preserve existing user state
Theoretical Basis
The data migration pattern follows a detect-transform-persist sequence:
# Pseudocode for user migration to org-based structure
def migrate_user(user_id, user_settings, user_info):
# Step 1: Create a personal organization for the user
personal_org = create_personal_org(
name=user_info["username"],
contact_email=user_info["email"],
)
# Step 2: Migrate LLM proxy entries to the new org scope
llm_manager.migrate_entries(
from_user=user_id,
to_org=personal_org.id,
)
# Step 3: Migrate billing state to the new org
billing_service.migrate_customer(
from_user=user_id,
to_org=personal_org.id,
)
# Step 4: Create the user record linked to the personal org
user = User(
id=user_id,
personal_org_id=personal_org.id,
settings=user_settings,
)
return user
Key considerations:
- Idempotency: The migration should be safe to run multiple times for the same user. If the user already has a personal organization, the migration should skip creation and proceed to verify completeness.
- Atomicity: All migration steps should be wrapped in a transaction so that partial migrations do not leave users in an inconsistent state.
- Billing continuity: Stripe customer IDs and subscription state must be transferred accurately to avoid double-charging or service interruption.
- Backward compatibility: After migration, all existing API endpoints and workflows should continue to function for the migrated user.