Class: ActionAgent::SandboxOrchestrator
- Inherits:
-
Object
- Object
- ActionAgent::SandboxOrchestrator
- Defined in:
- app/services/action_agent/sandbox_orchestrator.rb
Overview
SandboxOrchestrator
Unified interface for managing agent sandbox sessions. Supports multiple backends for cloud-agnostic deployment:
- incus: Self-hosted Incus containers (any Linux host)
- cloud_run: Google Cloud Run Jobs (serverless)
- kubernetes: Kubernetes pods (GKE, EKS, self-hosted k8s)
Configuration:
Set SANDBOX_BACKEND environment variable to choose backend.
Default: "incus" for simplicity
Usage:
orchestrator = SandboxOrchestrator.new
result = orchestrator.create_sandbox(session)
status = orchestrator.status(container_id)
orchestrator.terminate(container_id)
Defined Under Namespace
Classes: UnsupportedBackendError
Constant Summary collapse
- BUILT_IN_BACKENDS =
The engine ships only the in-memory backend. Anything that talks to real infrastructure (Incus, Kubernetes, Cloud Run) is registered by the app that operates it, so the engine carries none of those SDKs:
ActionAgent.sandbox_backends = { "cloud_run" => "CloudRunService" } { "mock" => "ActionAgent::MockSandboxBackend" }.freeze
- ADAPTER_METHODS =
Backends disagree on what to call each verb. Candidates are tried in order and the first the backend responds to wins, so a host-registered class needs no adapter of its own.
{ create: %i[create_sandbox create_sandbox_pod create_sandbox_job], status: %i[status container_status pod_status job_status], terminate: %i[terminate terminate_pod cancel_job], list: %i[list_sandboxes list_sandbox_pods list_jobs], cleanup: %i[cleanup_expired cleanup_expired_pods cleanup_expired_jobs] }.freeze
Instance Attribute Summary collapse
-
#backend_name ⇒ Object
readonly
Returns the value of attribute backend_name.
Class Method Summary collapse
-
.backends ⇒ Object
All backend names available in this install.
-
.default_backend ⇒ Object
The backend used when none is named: whatever the host app configured as sandbox_service, falling back to the in-memory one.
Instance Method Summary collapse
-
#available_tiers(category: nil) ⇒ Array<SandboxInstanceTier>
List available instance tiers.
-
#backend_info ⇒ Hash
Get backend-specific configuration info.
-
#cleanup_expired ⇒ Integer
Cleanup expired sandboxes.
-
#create_sandbox(sandbox_session, instance_tier: nil) ⇒ Hash
Create a new sandbox for the given session.
-
#get_tier(tier_id) ⇒ SandboxInstanceTier
Get a specific instance tier.
-
#healthy? ⇒ Boolean
Check if the backend is healthy.
-
#initialize(backend: nil) ⇒ SandboxOrchestrator
constructor
A new instance of SandboxOrchestrator.
-
#list_sandboxes ⇒ Array<Hash>
List all active sandboxes.
-
#status(sandbox_id) ⇒ Hash
Get the status of a sandbox.
-
#terminate(sandbox_id) ⇒ Boolean
Terminate a sandbox.
Constructor Details
#initialize(backend: nil) ⇒ SandboxOrchestrator
Returns a new instance of SandboxOrchestrator.
58 59 60 61 62 63 64 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 58 def initialize(backend: nil) @backend_name = (backend || self.class.default_backend).to_s class_name = self.class.backends[@backend_name] raise UnsupportedBackendError, "Unknown backend: #{@backend_name}" if class_name.nil? @backend = class_name.constantize.new end |
Instance Attribute Details
#backend_name ⇒ Object (readonly)
Returns the value of attribute backend_name.
66 67 68 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 66 def backend_name @backend_name end |
Class Method Details
.backends ⇒ Object
All backend names available in this install.
47 48 49 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 47 def self.backends BUILT_IN_BACKENDS.merge(ActionAgent.sandbox_backends.to_h.transform_keys(&:to_s)) end |
.default_backend ⇒ Object
The backend used when none is named: whatever the host app configured as sandbox_service, falling back to the in-memory one.
53 54 55 56 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 53 def self.default_backend name = ENV["SANDBOX_BACKEND"].presence || ActionAgent.sandbox_service.to_s backends.key?(name) ? name : "mock" end |
Instance Method Details
#available_tiers(category: nil) ⇒ Array<SandboxInstanceTier>
List available instance tiers
101 102 103 104 105 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 101 def available_tiers(category: nil) tiers = SandboxInstanceTier.available tiers = tiers.select { |t| t.category == category.to_s } if category tiers end |
#backend_info ⇒ Hash
Get backend-specific configuration info
161 162 163 164 165 166 167 168 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 161 def backend_info { name: @backend_name, class: @backend.class.name, healthy: healthy?, features: backend_features } end |
#cleanup_expired ⇒ Integer
Cleanup expired sandboxes
141 142 143 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 141 def cleanup_expired @backend.public_send(adapter_method(:cleanup)) end |
#create_sandbox(sandbox_session, instance_tier: nil) ⇒ Hash
Create a new sandbox for the given session
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 73 def create_sandbox(sandbox_session, instance_tier: nil) # Resolve tier tier = resolve_tier(instance_tier) method = adapter_method(:create) result = if accepts_instance_tier?(method) @backend.public_send(method, sandbox_session, instance_tier: tier) else @backend.public_send(method, sandbox_session) end # Normalize response format across backends { sandbox_id: result[:container_name] || result[:pod_name] || result[:job_name], url: result[:url], ip: result[:container_ip] || result[:pod_ip], backend: @backend_name, instance_tier: result[:instance_tier] || tier&.id, resources: result[:resources], hourly_cost: result[:hourly_cost] || tier&.hourly_cost&.to_f, created_at: result[:created_at] || Time.current } end |
#get_tier(tier_id) ⇒ SandboxInstanceTier
Get a specific instance tier
111 112 113 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 111 def get_tier(tier_id) SandboxInstanceTier.find(tier_id) end |
#healthy? ⇒ Boolean
Check if the backend is healthy
148 149 150 151 152 153 154 155 156 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 148 def healthy? # A backend that can list is reachable; one that cannot is assumed # healthy because there is nothing to probe. list_sandboxes if ADAPTER_METHODS[:list].any? { |m| @backend.respond_to?(m) } true rescue => e Rails.logger.error("Sandbox backend health check failed: #{e.}") false end |
#list_sandboxes ⇒ Array<Hash>
List all active sandboxes
134 135 136 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 134 def list_sandboxes @backend.public_send(adapter_method(:list)) end |
#status(sandbox_id) ⇒ Hash
Get the status of a sandbox
119 120 121 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 119 def status(sandbox_id) @backend.public_send(adapter_method(:status), sandbox_id) end |
#terminate(sandbox_id) ⇒ Boolean
Terminate a sandbox
127 128 129 |
# File 'app/services/action_agent/sandbox_orchestrator.rb', line 127 def terminate(sandbox_id) @backend.public_send(adapter_method(:terminate), sandbox_id) end |