OpenHands / OpenHands/enterprise

Support custom sandbox images in conversation start API

Open
#33 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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_id field to AppConversationStartRequest
  • Add mutual exclusion validation with sandbox_id
  • Pass sandbox_spec_id through to start_sandbox()
  • Implement DynamicSandboxSpecService OR RemoteSandboxSpecService
  • Add OH_ALLOWED_SANDBOX_IMAGE_PREFIXES configuration
  • Update OpenAPI documentation
  • Add tests for custom image selection
  • Update docs/examples

Files to Modify

  • openhands/app_server/app_conversation/app_conversation_models.py
  • openhands/app_server/app_conversation/live_status_app_conversation_service.py
  • openhands/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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.