Skip to content

refactor(python): async-native fleet, and handle release semantics - #88

Open
Sunny (sunnylovestiramisu) wants to merge 1 commit into
agent-substrate:mainfrom
sunnylovestiramisu:pr2
Open

Sunny (sunnylovestiramisu) wants to merge 1 commit into
agent-substrate:mainfrom
sunnylovestiramisu:pr2

Conversation

@sunnylovestiramisu

@sunnylovestiramisu Sunny (sunnylovestiramisu) commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces AsyncSandboxFleet, an asyncio-native orchestrator designed for high-concurrency workloads (such as reinforcement learning rollouts with VeRL, TorchRL, and Ray), standardizes sandbox handle lifecycle semantics, and integrates rootfs image resolution for unmodified container images via Substrate's derived template mechanism (#70).


Key Changes

1. AsyncSandboxFleet

  • Native Async Orchestrator: Introduces AsyncSandboxFleet using asyncio tasks, semaphores, and thread pools to enable non-blocking batch acquisition and warm-pool sizing without blocking the caller's event loop.
  • Async Setup & Sizing: Supports await fleet.setup(tasks=None) to run pre-flight, compute a FleetPlan, and warm actor pools concurrently across task images.
  • Batch & Windowed Warmup: Provides warm_images() and unwarm_image() to support rolling or sliding-window allocation strategies for bounded footprint execution.
  • Top-Level Export: Exported AsyncSandboxFleet from ate_env.

2. Handle Lifecycle & Release Semantics

  • Standardized Execution Results: Standardized SandboxHandle.exec() and exec_async() to return ExecResult.
  • Async Release & Recycle: Added SandboxHandle.release_async() and recycle_async(), explicitly documenting that recycling suspends the actor without resetting in-guest filesystem or memory state.
  • Safer Context Manager Defaults: Changed SandboxHandle context manager (__exit__ / __aexit__) to default to release() (terminate) rather than destroying the entire fleet run.

3. Task Image & Rootfs Resolution (Addressing Review Feedback)

  • _ensure_template_for_image Resolution: Returns (template, rootfs_image):
    • Mapped images in config.image_templates resolve to (mapped_template, None).
    • Unmapped task images resolve to (config.template or "default-template", image), passing the task image through as rootfs_image while using the configured template as base.
  • BackendDriver Signature Updates: Added rootfs_image: Optional[str] = None parameter across acquire, acquire_async, warm_pool, warm_pool_async, and unwarm_pool in BackendDriver, SubstrateBackendDriver, and MockBackendDriver.
  • Client.create Propagation: Passed rootfs_image=rootfs_image at both client.create invocations in SubstrateBackendDriver (on-demand creation in _acquire_internal and pre-warm in warm_pool_async).
  • Pool Keying by (template, image): Keyed _warm_paused_pool by (template, image) tuple to prevent warm pool collisions between environments sharing a base template with distinct rootfs images, while retaining transparent string key normalization for backwards compatibility.
  • Metadata Propagation: Propagated rootfs_image onto RawSandboxInstance and PlanEntry, and correctly preserved the pool key upon release_async(..., recycle=True).
  • Task Image Docstring: Documented Task.image behavior detailing lookup in image_templates versus propagation as rootfs_image.

Review Feedback Addressed

  • Image Propagation to Server: Fixed issue where FleetConfig with Task(image="...") sent template.name == "default-template" and empty rootfs_image, ignoring the task container image.
  • Per-Image Template Derivation: Leveraged Client.create(rootfs_image=...) introduced in Run unmodified rootfs images: guest injected as an OCI image volume, plus second-runtime layers #70 so the Substrate server derives actor templates per digest on the fly.
  • Pool Key Alignment: Keyed _warm_paused_pool by (template, image).

Verification

  • Unit Tests:

    • Added test_substrate_rootfs_image_sent_to_server in test_substrate_driver.py verifying that:
      1. Default FleetConfig sends template.name == "default-template" and last_create_request.rootfs_image == image.
      2. AsyncSandboxFleet correctly propagates rootfs_image during async acquisition.
      3. driver.warm_pool_async(..., rootfs_image=image) passes rootfs_image and keys _warm_paused_pool[("custom-base", image)].
      4. Mapped images in image_templates send rootfs_image == "" and use the mapped template.
    • Added test_async_fleet.py covering concurrency, timeouts, and batch acquisition.
    • Ran full test suite: 124 passed, 29 skipped.
  • End to End

    • Tested with creating 1000 sandboxes with only python-slim image

…R 2)

Introduce AsyncSandboxFleet as an asyncio-native pool manager and refine handle
lifecycle semantics:

- Introduce AsyncSandboxFleet using pure asyncio queues, locks, and tasks
  for non-blocking concurrent rollout acquisition.
- Support async setup(tasks=None) to pre-warm the pool from a task list.
- Standardize SandboxHandle.exec() and exec_async() to return ExecResult.
- Add SandboxHandle.release_async() and recycle_async(), documenting that
  recycling suspends the actor without resetting in-guest filesystem or memory state.
- Change SandboxHandle context manager (__exit__ / __aexit__) to default
  to release() rather than teardown/destroy.
- Export AsyncSandboxFleet in ate_env top-level module.
- Add test_async_fleet.py covering concurrency, timeouts, and batch acquisition.
- Resolve task images to (template, rootfs_image) for Substrate backends and pass rootfs_image to Client.create.
@sunnylovestiramisu

Sunny (sunnylovestiramisu) commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressed the nit pick in PR #89 (review)

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.

1 participant