SAAS: Introducing orgs (phase 1)
Introducing orgs to OpenHands SAAS, this is an intermediate step we're calling phase 1
The planned phases are
- Phase 1: introduce orgs structure in BE where every user is a personal workspace org by default (there should be no change existing product behavior while the new structure is introduced)
- Phase 2: introduce helper utils for orgs for parity with existing app (automatically migrating default LLM models, determine super admin role, checking BE work to simplify self-hosted installs/transitions to orgs)
- Phase 3: integrate FE with BE (FE to create new orgs, FE to manage org members, new BE api routes to interact with orgs)
EDIT: WRT to phase 1, still working on
- resolving some failing tests
- adding new ones to coverer migration logic
- figuring out how to set defaults for llm provider on the org level
To run this PR locally, use the following command:
GUI with Docker:
docker run -it --rm -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock --add-host host.docker.internal:host-gateway -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:dec0f41-nikolaik --name openhands-app-dec0f41 docker.openhands.dev/openhands/openhands:dec0f41
⚠️ This PR contains migrations. Please synchronize before merging to prevent conflicts.
OH generated mermaid chart for DB schemas
erDiagram
%% Core Organization Tables
role {
int id PK "Identity, Primary Key"
string name UK "Unique, e.g., 'admin', 'user'"
int rank "Permission rank (1=admin, 1000=user)"
}
org {
uuid id PK "Primary Key, gen_random_uuid()"
string name UK "Unique organization name"
string contact_name "Optional contact person"
string contact_email "Optional contact email"
int conversation_expiration "Optional expiration setting"
string agent "Agent configuration"
int max_iterations "Max iterations setting"
string security_analyzer "Security analyzer config"
boolean confirmation_mode "Default false"
string llm_model "LLM model setting"
string _llm_api_key_for_byor "Encrypted BYOR API key"
string llm_base_url "LLM base URL"
int remote_runtime_resource_factor "Resource factor"
boolean enable_default_condenser "Default true"
float billing_margin "Billing margin"
boolean enable_proactive_conversation_starters "Default true"
string sandbox_base_container_image "Container image"
string sandbox_runtime_container_image "Runtime image"
int org_version "Default 0"
json mcp_config "MCP configuration"
string _search_api_key "Encrypted search API key"
string _sandbox_api_key "Encrypted sandbox API key"
float max_budget_per_task "Budget limit"
boolean enable_solvability_analysis "Default false"
}
user {
int id PK "Identity, Primary Key"
string keycloak_user_id UK "Unique Keycloak user ID"
uuid current_org_id FK "Current organization"
int role_id FK "Role in current org"
datetime accepted_tos "Terms of service acceptance"
boolean enable_sound_notifications "Default false"
string language "User language preference"
boolean user_consents_to_analytics "Analytics consent"
string email "User email"
boolean email_verified "Email verification status"
}
org_user {
uuid org_id PK,FK "Organization ID"
int user_id PK,FK "User ID"
int role_id FK "Role in this org"
string _llm_api_key "Encrypted LLM API key"
string status "Membership status"
}
%% Related Tables with org_id Foreign Keys
billing_sessions {
string id PK "Billing session ID"
string user_id "User identifier"
uuid org_id FK "Organization ID"
enum status "in_progress, completed, cancelled, error"
enum billing_session_type "DIRECT_PAYMENT, MONTHLY_SUBSCRIPTION"
decimal price "Session price"
string price_code "Price code"
datetime created_at "Creation timestamp"
datetime updated_at "Update timestamp"
}
conversation_metadata {
string conversation_id PK "UUID conversation ID"
string github_user_id "GitHub user ID"
string user_id "Keycloak user ID"
uuid org_id FK "Organization ID"
string selected_repository "Repository name"
string selected_branch "Git branch"
string git_provider "GitHub, GitLab, etc."
string title "Conversation title"
datetime last_updated_at "Last update"
datetime created_at "Creation time"
string trigger "Trigger information"
json pr_number "List of PR numbers"
float accumulated_cost "Total cost"
int prompt_tokens "Prompt token count"
int completion_tokens "Completion token count"
int total_tokens "Total token count"
string llm_model "LLM model used"
}
api_keys {
int id PK "Auto-increment primary key"
string key UK "Unique API key"
string user_id "User identifier"
uuid org_id FK "Organization ID"
string name "API key name"
datetime created_at "Creation timestamp"
datetime last_used_at "Last usage time"
datetime expires_at "Expiration time"
}
user_secrets {
int id PK "Identity, Primary Key"
string keycloak_user_id "Keycloak user ID"
uuid org_id FK "Organization ID"
string secret_name "Secret name"
string secret_value "Encrypted secret value"
string description "Optional description"
}
gitlab_webhook {
int id PK "Auto-increment primary key"
string group_id "GitLab group ID"
string project_id "GitLab project ID"
string user_id "User identifier"
uuid org_id FK "Organization ID"
boolean webhook_exists "Webhook existence flag"
string webhook_url "Webhook URL"
string webhook_secret "Webhook secret"
string webhook_uuid "Webhook UUID"
text scopes "Webhook scopes (array)"
datetime last_synced "Last sync timestamp"
}
slack_conversation {
int id PK "Identity, Primary Key"
string conversation_id "Conversation identifier"
string channel_id "Slack channel ID"
string keycloak_user_id "Keycloak user ID"
uuid org_id FK "Organization ID"
string parent_id "Parent message ID"
}
slack_users {
int id PK "Identity, Primary Key"
string keycloak_user_id "Keycloak user ID"
uuid org_id FK "Organization ID"
string slack_user_id "Slack user ID"
string slack_display_name "Slack display name"
datetime created_at "Creation timestamp"
}
stripe_customers {
int id PK "Auto-increment primary key"
string keycloak_user_id "Keycloak user ID"
uuid org_id FK "Organization ID"
string stripe_customer_id "Stripe customer ID"
datetime created_at "Creation timestamp"
datetime updated_at "Update timestamp"
}
user_repos {
int id PK "Auto-increment primary key"
string user_id "User identifier"
uuid org_id FK "Organization ID"
string repo_id "Repository identifier"
boolean admin "Admin access flag"
}
%% Relationships
role ||--o{ user : "has role"
role ||--o{ org_user : "defines role in org"
org ||--o{ user : "current_org"
org ||--o{ org_user : "belongs to"
org ||--o{ billing_sessions : "has billing sessions"
org ||--o{ conversation_metadata : "owns conversations"
org ||--o{ api_keys : "manages API keys"
org ||--o{ user_secrets : "stores secrets"
org ||--o{ gitlab_webhook : "has webhooks"
org ||--o{ slack_conversation : "has slack conversations"
org ||--o{ slack_users : "has slack users"
org ||--o{ stripe_customers : "has stripe customers"
org ||--o{ user_repos : "manages user repos"
user ||--o{ org_user : "member of orgs"
user ||--o{ billing_sessions : "creates billing sessions"
user ||--o{ conversation_metadata : "owns conversations"
user ||--o{ api_keys : "owns API keys"
user ||--o{ user_secrets : "has secrets"
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 13, 2025, 12:10:15 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 16, 2025, 04:50:56 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 16, 2025, 05:48:59 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 16, 2025, 06:11:37 PM ET
Patched a couple of things confirmed that slack integration is working
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 16, 2025, 10:34:03 PM ET
@OpenHands please fix the failing actions on PR #11265 at branch migrate-org-db-litellm-from-deploy
I'm on it! chuckbutkus can track my progress at all-hands.dev
Summary of Work Completed
I successfully fixed the failing GitHub Actions on PR #11265 for the migrate-org-db-litellm-from-deploy branch. Here's what was accomplished:
Issues Identified and Fixed:
✅ Linting Issues (Ruff)
- Fixed import ordering in
sql_app_conversation_info_service.py - Moved
SQL_UUIDimport to separate import block to satisfy ruff formatting rules
✅ Database Schema Issues (Python Tests)
- Root cause: The
StoredConversationMetadatamodel had foreign key constraints referencing anorgtable that doesn't exist in the test environment - Solution:
- Changed
org_idcolumn fromnullable=Falsetonullable=Trueto match the migration - Removed
ForeignKey('org.id')constraint that was causing SQLAlchemy errors - Commented out the
relationship('Org')to prevent relationship resolution errors - Added explanatory comments in both files
- Changed
Verification Results:
- ✅ All pre-commit linting checks pass
- ✅ All SQL conversation info service tests pass (17/17)
- ✅ Full test suite passes (except 1 unrelated frontend test requiring Node.js)
- ✅ Changes pushed to PR successfully
Changes Made:
The changes are minimal and targeted, addressing only the specific issues causing the CI failures:
-
File:
openhands/app_server/app_conversation/sql_app_conversation_info_service.py- Fixed import ordering
- Made
org_idnullable and removed foreign key constraint - Commented out org relationship
-
File:
tests/unit/app_server/test_sql_app_conversation_info_service.py- Added explanatory comment about org_id column
The PR should now pass the failing GitHub Actions for Lint and Python Tests. The database schema changes maintain compatibility with the enterprise migration while allowing tests to run successfully in environments where the org table doesn't exist.
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 17, 2025, 12:58:30 AM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 20, 2025, 08:45:13 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 21, 2025, 12:08:03 PM ET
TODO
- [x] verify behavior for
CURRENT_USER_SETTINGS_VERSION. Ideally it migrates any user in personal OH workspaces not on a pro-subscription. We ideally migrate the personal workspace default LLM. - [ ] add default llms to Orgs where LLM is from OH provider
- [x] avoid deleting old tables contents during org migration so rollbacks can be easily executed
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 21, 2025, 01:56:09 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 21, 2025, 04:28:05 PM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 22, 2025, 10:01:58 AM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 22, 2025, 10:55:16 AM ET
Feature deployment is available at: https://ohpr-11265.staging.all-hands.dev Redirect URI for GitHub App is https://ohpr-11265.auth.staging.all-hands.dev/realms/allhands/broker/github/endpoint Last updated: Oct 23, 2025, 11:52:49 AM ET
Coverage report
The report is truncated to 25 files out of 44. To see the full report, please visit the workflow summary page. This report was generated by python-coverage-comment-action
Click to see where and how coverage changed
How you thought about how easy or hard it will be to transition over to asyncdb once the V0 conversation code has been removed? Hopefully it will not be difficult.
Looks like there are a few issues preventing this PR from being merged!
- GitHub Actions are failing:
- Run Python Tests
- Docker
- Lint
If you'd like me to help, just leave a comment, like
@OpenHands please fix the failing actions on PR #11265 at branch `migrate-org-db-litellm-from-deploy`
Feel free to include any additional details that might help me get this PR into a better state.
You can manage your notification settings