Skip to content

Conversation

@ShenJunkun
Copy link

Description

This PR addresses #65 by replacing the rigid SandboxType enum with a string-based identifier. This change allows users to register custom sandbox types beyond the built-in options, supporting diverse scenarios like domain-specific images or custom security policies.

Changes

  • Replaced SandboxType enum with String in @RegisterSandbox and registry logic.
  • Defined SandboxType constants to maintain backward compatibility.
  • Updated the identification key to (userId, sessionId, sandboxType).

Checklist

  • My code follows the code style of this project.
  • I have performed a self-review of my own code.
  • All new and existing tests passed.

@cla-assistant
Copy link

cla-assistant bot commented Dec 29, 2025

CLA assistant check
All committers have signed the CLA.

@cla-assistant
Copy link

cla-assistant bot commented Dec 29, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@xuehuitian45
Copy link
Collaborator

Hello,I noticed in your PR that SandboxType has been changed to a String type, which is great. However, I encountered the following issue during usage:

When I followed the instructions in the previous cookbook to implement a custom sandbox, I set SandboxType to SandboxType.Custom and assigned my desired name to CustomType. However, the sandbox was not created using my specified imageId—it fell back to the default behavior. In contrast, using the original approach (without CustomType) works correctly.

I found the root cause lies in the following area:

Image lookup logic:
During sandbox creation in SandboxManager.java, the system calls SandboxRegistryService.getImageByType() to retrieve the image name. However, this method only retrieves CustomType in typeConfigRegistry, which cannot be found. Actually, the custom types are only registered in customTypeRegistry.

To solve this problem, I modified the getConfigByType method in SandboxRegistry as follows:

return Optional.ofNullable(typeConfigRegistry.get(sandboxType))
.or(() -> Optional.ofNullable(customTypeRegistry.get(sandboxType)));

This partially resolves the issue when there is only one custom sandbox per session, but the fundamental problem remains: the system still uses SandboxType (i.e., "Custom") instead of the actual custom type name (CustomType) for sandbox identification and management. While workarounds are possible, this design is not elegant and will likely confuse users.

Also, I found that there could also be a problem harder to encounter:

Sandbox identity/key management:
The SandboxManager uses a composite key - SandboxKey consisting of userId, sessionId, sandboxType, and (only in AgentBay cases) imageId to manage created sandboxes. Since all custom sandboxes now share the same sandboxType (SandboxType.Custom), multiple distinct custom sandboxes within the same session cannot be properly distinguished, leading to incorrect ContainerModel retrieval.

My Recommendation

I suggest completely deprecating the SandboxType enum concept and instead using a plain String as the universal, first-class identifier for all sandbox types—both built-in and custom.

Specifically:

  • Eliminate the distinction between "predefined types" and "custom types".
  • Treat every sandbox type (e.g., "browser", "filesystem", "my-custom") as an equal string identifier.
  • Remove the CustomType field entirely; the sandbox type is the string name.

This simplifies the architecture, avoids key collisions, and provides a consistent user experience.

Note: Given your recent changes, please also update the relevant documentation in the Cookbook folder accordingly and add stricter tests.

@ShenJunkun
Copy link
Author

Thanks for the suggestions! Here is my plan for the modifications. Please let me know if this looks good or if further adjustments are needed.

  1. Remove customType: I will remove customType from RegisterSandbox.java. Moving forward, SandboxType will be used for both built-in and custom sandboxes.

  2. Clean up redundant code: Since customType is being removed, I will clean up the redundant code that relies on it, including:

    1. Logic in SandboxAnnotationProcessor.java:

      if (customType != null && !customType.isEmpty()) {
      SandboxRegistryService.registerCustomType(
      customType,
      imageName,
      resourceLimits,
      securityLevel,
      timeout,
      description,
      environment,
      runtimeConfig
      );
      logger.info("Registered custom sandbox via annotation: type={}, class={}, image={}",
      customType, clazz.getSimpleName(), imageName);
      } else {

    2. The customTypeRegistry variable in SandboxRegistry.java:

      private static final Map<String, SandboxConfig> customTypeRegistry = new ConcurrentHashMap<>();

    3. Registration logic for custom sandboxes in SandboxRegistry.java:

      public static void registerCustomType(
      String typeName,
      String imageName,
      Map<String, Object> resourceLimits,
      String securityLevel,
      int timeout,
      String description,
      Map<String, String> environment,
      Map<String, Object> runtimeConfig) {
      DynamicSandboxType.custom(typeName);
      SandboxConfig config = new SandboxConfig.Builder()
      .sandboxType(SandboxType.BASE) // Placeholder
      .imageName(imageName)
      .resourceLimits(resourceLimits)
      .securityLevel(securityLevel)
      .timeout(timeout)
      .description(description)
      .environment(environment)
      .runtimeConfig(runtimeConfig)
      .build();
      customTypeRegistry.put(typeName.toLowerCase(), config);
      logger.info("Registered custom sandbox type with full config: name={}, image={}, timeout={}s", typeName, imageName, timeout);
      }
      /**
      * Get configuration for a custom sandbox type by name
      *
      * @param typeName The custom type name
      * @return Optional containing the configuration if found
      */
      public static Optional<SandboxConfig> getCustomTypeConfig(String typeName) {
      if (typeName == null) {
      return Optional.empty();
      }
      return Optional.ofNullable(customTypeRegistry.get(typeName.toLowerCase()));
      }
      /**
      * Get image name for a custom sandbox type
      *
      * @param typeName The custom type name
      * @return Optional containing the image name if found
      */
      public static Optional<String> getCustomTypeImage(String typeName) {
      return getCustomTypeConfig(typeName).map(SandboxConfig::getImageName);
      }
      /**
      * Check if a custom type is registered
      *
      * @param typeName The custom type name
      * @return true if registered, false otherwise
      */
      public static boolean isCustomTypeRegistered(String typeName) {
      if (typeName == null) {
      return false;
      }
      return customTypeRegistry.containsKey(typeName.toLowerCase());
      }

  3. Update Tests: I will modify the tests in CustomSandboxTest.java to align with these logic changes.

    @RegisterSandbox(
    imageName = "agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-custom_sandbox:latest",
    customType = "custom_sandbox",
    securityLevel = "medium",
    timeout = 60,
    description = "my sandbox",
    environment = {
    "TAVILY_API_KEY=${TAVILY_API_KEY}",
    "AMAP_MAPS_API_KEY=${AMAP_MAPS_API_KEY}"
    }
    )
    public static class MyCustomSandbox extends Sandbox {
    public MyCustomSandbox(
    SandboxManager managerApi,
    String userId,
    String sessionId) {
    super(managerApi, userId, sessionId, SandboxType.BASE, 60);
    }
    }
    @RegisterSandbox(
    imageName = "my-registry/my-advanced-sandbox:latest",
    customType = "advanced_sandbox",
    securityLevel = "high",
    timeout = 120,
    description = "Advanced custom sandbox with full configuration",
    environment = {
    "API_KEY=${API_KEY}",
    "DEBUG_MODE=true",
    "MAX_WORKERS=4"
    },
    resourceLimits = {
    "memory=4g",
    "cpu=4.0"
    },
    runtimeConfig = {
    "enable_gpu=true",
    "max_connections=100"
    }
    )
    public static class AdvancedCustomSandbox extends Sandbox {
    public AdvancedCustomSandbox(
    SandboxManager managerApi,
    String userId,
    String sessionId) {
    super(managerApi, userId, sessionId, SandboxType.BASE, 120);

  4. Update Documentation: I will update the usage instructions in both the English and Chinese versions of the cookbook.

I look forward to your feedback.

@xuehuitian45
Copy link
Collaborator

Your plan looks good to me. I'm looking forward to see your modification.

@ShenJunkun
Copy link
Author

@xuehuitian45 Hi, I have completed my planned changes. Please review my code and let me know if any further adjustments are needed. The new commits are listed below:

  • refactor(RegisterSandbox): Remove customType in RegisterSandbox
  • refactor: clean up redundant code
  • update CustomSandboxTest test case
  • feat: update docs about custom sandbox

@chickenlj chickenlj changed the base branch from main to 1.0.2-sandbox January 8, 2026 09:13
Copy link
Collaborator

@xuehuitian45 xuehuitian45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@xuehuitian45 xuehuitian45 merged commit a5371d9 into agentscope-ai:1.0.2-sandbox Jan 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants