Custom Sandbox Engine Integration Guide

December 20, 2025 · View on GitHub

This document explains how to integrate a custom sandbox engine into your system using two supported approaches. Follow the instructions and code examples below based on your requirements.


Overview

The system provides two integration paths for custom sandbox engines:

  • Option 1 (Standard): Adapt your existing sandbox API to the predefined EnvInstanceClient interface.
  • Option 2 (Custom): Implement the EnvInstanceService interface directly in your own client, allowing full control over API interactions.

Both methods require implementing CRUD operations and lifecycle management for sandbox instances. The core interface is defined as:

type EnvInstanceService interface {
GetEnvInstance(id string) (*models.EnvInstance, error)
CreateEnvInstance(req *backend.Env) (*models.EnvInstance, error)
DeleteEnvInstance(id string) error
ListEnvInstances(envName string) ([]*models.EnvInstance, error)
Warmup(req *backend.Env) error
Cleanup() error
}

Configuration is controlled by the schedule_type flag in your main function:

  • schedule_type: standard → Uses EnvInstanceClient
  • schedule_type: custom → Uses your implementation of EnvInstanceService

Option 1: Adapt Existing API via EnvInstanceClient

Use this approach if your sandbox API can be adapted to match the expected request/response format of EnvInstanceClient.

Step 1: Configure Base URL

Initialize the client with your sandbox engine’s base URL, which can be specified by schedule_addr flag in api-service startup command:

client := service.NewEnvInstanceClient("https://your-sandbox-api.com")

Step 2: Ensure API Compatibility

Your sandbox service must expose the following endpoints:

MethodURI PatternDescription
POST/aenvironment/instanceCreate a new environment instance
GET/aenvironment/instance/{id}Get instance by ID
DELETE/aenvironment/instance/{id}Delete instance by ID
GET/aenvironment/instance?envName={envName}List instances by environment name
PUT/aenvironment/instance/action/warmupWarm up environment resources
PUT/aenvironment/instance/action/cleanupCleanup unused resources

API Details & Examples

1. Create Environment Instance

Endpoint: POST /aenvironment/instance
Request Body:

{
  "id": "env-123",
  "name": "Python3.9-RL",
  "description": "Reinforcement Learning sandbox",
  "version": "v1.2",
  "tags": [
    "ml",
    "rl"
  ],
  "code_url": "https://github.com/user/rl-env.git",
  "build_config": {
    "image": "python:3.9-rl",
    "timeout": 600
  },
  "test_config": {
    "command": "pytest tests/"
  },
  "deploy_config": {
    "replicas": 1,
    "resources": {
      "cpu": "2",
      "memory": "4Gi"
    }
  }
}

Success Response (200/201):

{
  "success": true,
  "code": 200,
  "message": "Instance created",
  "data": {
    "id": "inst-789",
    "env": {
      /* same as request env */
    },
    "status": "Running",
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:00:00Z",
    "ip": "10.244.1.10",
    "ttl": "2h"
  }
}

2. Get Environment Instance

Endpoint: GET /aenvironment/instance/{id}

Response:

{
  "success": true,
  "code": 200,
  "data": {
    "id": "inst-789",
    "env": {
      /* env config */
    },
    "status": "Running",
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:30:00Z",
    "ip": "10.244.1.10",
    "ttl": "2h"
  }
}

3. Delete Environment Instance

Endpoint: DELETE /aenvironment/instance/{id}
Response:

{
  "success": true,
  "code": 200,
  "data": true
}

4. List Instances by Environment Name

Endpoint: GET /aenvironment/instance?envName=Python3.9-RL
Response:

{
  "success": true,
  "code": 200,
  "data": [
    {
      "id": "inst-789",
      "env": {
        "name": "Python3.9-RL"
      },
      "status": "Running",
      "ip": "10.244.1.10",
      "created_at": "2025-01-15T10:00:00Z"
    }
  ]
}

5. Warmup (Prepare Resources)

Endpoint: PUT /aenvironment/instance/action/warmup
Request: Same body format as CreateEnvInstance (sent in body or inferred by service).
Response: Same as GetEnvInstance (containing warmed-up instance).


6. Cleanup (Release Unused Resources)

Endpoint: PUT /aenvironment/instance/action/cleanup
Response: Success or error message. Implementation may scan and delete expired instances.


Step 3: Initialize with Standard Mode

In your main.go:

func main() {
// Load config where schedule_type is set
config := loadConfig()

var envSvc service.EnvInstanceService

if config.ScheduleType == "standard" {
envSvc = service.NewEnvInstanceClient(config.SandboxBaseURL)
} else if config.ScheduleType == "custom" {
envSvc = NewCustomSandboxClient(config) // See Option 2
}

// Pass envSvc to scheduler or manager
scheduler := NewScheduler(envSvc)
scheduler.Start()
}

Pros: Quick integration if API matches.
⚠️ Cons: Requires adapting your API to expected URL, payload, and response formats.


Option 2: Implement Custom Client via EnvInstanceService

Use this if your sandbox API differs significantly in endpoints, parameters, or response structure.

Step 1: Define Your Custom Client

Implement all methods of EnvInstanceService:

type CustomSandboxClient struct {
baseURL    string
httpClient *http.Client
apiKey     string // Example: custom auth
}

func NewCustomSandboxClient(baseURL, apiKey string) *CustomSandboxClient {
return &CustomSandboxClient{
baseURL: baseURL,
apiKey:  apiKey,
httpClient: &http.Client{
Timeout: 45 * time.Second,
},
}
}

Step 2: Implement Required Methods (Example: Create)

func (c *CustomSandboxClient) CreateEnvInstance(req *backend.Env) (*models.EnvInstance, error) {
// Custom endpoint and payload
url := fmt.Sprintf("%s/v2/envs/launch", c.baseURL)

payload := map[string]interface{}{
"template": req.Name,
"git":      req.CodeURL,
"resources": req.DeployConfig,
}

jsonData, _ := json.Marshal(payload)
httpReq, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
httpReq.Header.Set("X-API-Key", c.apiKey)
httpReq.Header.Set("Content-Type", "application/json")

resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 201 {
return nil, fmt.Errorf("custom create failed: %s", body)
}

// Parse custom response format
var result struct {
SandboxID string `json:"sandbox_id"`
IP        string `json:"endpoint_ip"`
Status    string `json:"state"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}

return &models.EnvInstance{
ID:     result.SandboxID,
Env:    req,
Status: result.Status,
IP:     result.IP,
}, nil
}

You must implement all 6 interface methods.


Step 3: Wire Custom Client in Main

func main() {
var scheduleClient service.EnvInstanceService
if scheduleType == "k8s" {
scheduleClient = service.NewScheduleClient(scheduleAddr)
} else if scheduleType == "standard" {
scheduleClient = service.NewEnvInstanceClient(scheduleAddr)
} else if scheduleType == "custom" {
scheduleClient = service.NewCustomSandboxClient(scheduleAddr, "your-api-key")
} else {
log.Fatalf("unsupported schedule type: %v", scheduleType)
}

}

Pros: Full flexibility in API design, headers, auth, and response parsing.
⚠️ Requirement: You must handle all error cases, timeouts, and data mapping.


Required Data Structures

backend.Env (Input for Creation/Warmup)

{
  "id": "env-123",
  "name": "Python3.9",
  "description": "Standard Python env",
  "version": "v1.0",
  "tags": [
    "ai",
    "stable"
  ],
  "code_url": "https://github.com/org/repo.git",
  "build_config": {
    "image": "python:3.9"
  },
  "test_config": {
    "command": "make test"
  },
  "deploy_config": {
    "cpu": "2",
    "memory": "4Gi"
  }
}

models.EnvInstance (Return Type)

{
  "id": "inst-abc123",
  "env": {
    /* Env object */
  },
  "status": "Running",
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:05:00Z",
  "ip": "192.168.1.100",
  "ttl": "1h"
}

models.ClientResponse[T]

All responses must follow this envelope format:

{
  "success": true,
  "code": 200,
  "message": "OK",
  "data": {
    /* T payload */
  }
}

If your API uses a different envelope, transform it in your custom client methods.


Recommendations & Best Practices

  1. Error Handling
    Always return descriptive errors including HTTP status and response body (truncated if large).

  2. Timeout Configuration
    Set reasonable timeouts (e.g., 30–60s) based on sandbox startup time.

  3. Idempotency
    Ensure DeleteEnvInstance is idempotent—returning success if resource is already gone is acceptable.

  4. Logging
    Log key operations (e.g., Creating sandbox for env=Python3.9), especially in Cleanup.

  5. Health Checks
    Consider adding /health endpoint validation during client initialization.

  6. Testing
    Provide a mock implementation of EnvInstanceService for unit testing business logic.


Troubleshooting

SymptomLikely CauseSolution
404 Not FoundIncorrect base URL or endpoint pathVerify full URL construction in client
400 Bad RequestMismatched request payloadEnsure backend.Env.ToJSON() matches expected schema
unmarshal errorResponse format differsIn custom client, map your response to ClientResponse[T]
Cleanup does nothingFilterPods not implemented or returns emptyImplement filtering logic or return mock data

Summary

  • Standard Integration: Fast path if your API matches expected routes/payloads.
  • Custom Integration: Implement EnvInstanceService fully for maximum control.
  • Set schedule_type: standard or custom in config to select mode.
  • All responses must conform to ClientResponse[T] structure or be adapted.

For further details, refer to:

  • env_instance.go – Standard HTTP client implementation
  • schedule_client.go – Adapter example (ScheduleClient wraps pod ops into EnvInstanceService)
  • models/ – Shared data structures

Contact the platform team if you need assistance adapting your sandbox API.