OpenHands / OpenHands/enterprise
Support custom sandbox images in conversation start API
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 4
- Forks
- 2
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 101
Description
Problem Statement
With runtime-api able to manage warm pools of custom images (OpenHands/runtime-api#595), we still need a way for users to select which image to use when starting a conversation.
Currently:
- No API parameter to specify custom sandbox image
- No backend support to accept custom images
- Users are locked to the single default runtime image
Proposed Solution
Add sandbox_spec_id parameter to the conversation start API, allowing users to specify which custom image to use.
API Changes
Add Field to AppConversationStartRequest
# openhands/app_server/app_conversation/app_conversation_models.py
class AppConversationStartRequest(OpenHandsModel):
sandbox_id: str | None = Field(
default=None,
description='ID of an existing sandbox to use. Mutually exclusive with sandbox_spec_id.',
)
sandbox_spec_id: str | None = Field( # NEW
default=None,
description='Image/spec ID for creating a new sandbox. Mutually exclusive with sandbox_id.',
)
# ... rest of fields ...
@model_validator(mode='after')
def validate_sandbox_params(self) -> 'AppConversationStartRequest':
if self.sandbox_id and self.sandbox_spec_id:
raise ValueError(
'Cannot specify both sandbox_id and sandbox_spec_id'
)
return self
Pass Through to Sandbox Service
# openhands/app_server/app_conversation/live_status_app_conversation_service.py
async def _wait_for_sandbox_start(
self, task: AppConversationStartTask
) -> AsyncGenerator[AppConversationStartTask, None]:
if not task.request.sandbox_id:
sandbox = await self.sandbox_service.start_sandbox(
sandbox_id=sandbox_id_str,
sandbox_spec_id=task.request.sandbox_spec_id, # NEW: Pass through
)
Backend Changes
Support Custom Images in SandboxSpecService
Two approaches (choose one):
Option A: Dynamic resolution with allowed prefixes
# openhands/app_server/sandbox/dynamic_sandbox_spec_service.py
class DynamicSandboxSpecService(SandboxSpecService):
allowed_prefixes: list[str] # e.g., ["harbor.c24.de/"]
async def get_sandbox_spec(self, sandbox_spec_id: str) -> SandboxSpecInfo | None:
# Check if image matches any allowed prefix
for prefix in self.allowed_prefixes:
if sandbox_spec_id.startswith(prefix):
return SandboxSpecInfo(
id=sandbox_spec_id,
command=self.default_command,
initial_env=self.default_initial_env,
working_dir=self.default_working_dir,
)
return None
Option B: Fetch specs from runtime-api
# openhands/app_server/sandbox/remote_sandbox_spec_service.py
class RemoteSandboxSpecService(SandboxSpecService):
async def get_sandbox_spec(self, sandbox_spec_id: str) -> SandboxSpecInfo | None:
# Fetch from runtime-api GET /sandbox-specs (or /warm-runtime-configs)
specs = await self._fetch_specs_from_runtime_api()
return specs.get(sandbox_spec_id)
Configuration
Add environment variable to configure allowed image prefixes:
OH_ALLOWED_SANDBOX_IMAGE_PREFIXES="harbor.c24.de/,ghcr.io/openhands/"
Parse in config and inject into DynamicSandboxSpecService.
End-to-End Flow
# User starts conversation with custom image
POST /api/v1/app-conversations
{
"sandbox_spec_id": "harbor.c24.de/c24/pemservice:latest",
"github_repo": "https://github.com/company/php-project",
"agent_class": "CodeActAgent",
"language": "en"
}
# Backend:
# 1. Validates image matches allowed prefix
# 2. Passes to runtime-api: POST /start with image spec
# 3. Runtime-api claims warm runtime from pool
# 4. Conversation starts with custom image
Implementation Checklist
- Add
sandbox_spec_idfield toAppConversationStartRequest - Add mutual exclusion validation with
sandbox_id - Pass
sandbox_spec_idthrough tostart_sandbox() - Implement
DynamicSandboxSpecServiceORRemoteSandboxSpecService - Add
OH_ALLOWED_SANDBOX_IMAGE_PREFIXESconfiguration - Update OpenAPI documentation
- Add tests for custom image selection
- Update docs/examples
Files to Modify
openhands/app_server/app_conversation/app_conversation_models.pyopenhands/app_server/app_conversation/live_status_app_conversation_service.pyopenhands/app_server/sandbox/(new service or update existing)openhands/app_server/config.py(env var parsing)
Dependencies
- Recommended: OpenHands/runtime-api#595 merged first
- Recommended: OpenHands/runtime-api#596 for verifying images are ready
Related
- Parent: OpenHands/enterprise#32
- Technical design: C24 Custom Sandbox Images - Issues 1 & 2
- runtime-api: OpenHands/runtime-api#595, OpenHands/runtime-api#596
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with openhands/app_server/app_conversation/app_conversation_models.py and trace sandbox startup through live_status_app_conversation_service.py into the existing sandbox services. Review the configuration and sandbox service implementations before choosing the dynamic or remote approach. Done means sandbox_spec_id is validated and passed through, allowed images are supported, OpenAPI and docs are updated, and custom image selection and mutual exclusion are covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100