yao/agent/robot/executor/sandbox/executor.go
Max 41e0544aba Refactor Executor Architecture and Update Documentation
- Introduced multiple executor modes (Standard, DryRun, Sandbox) to accommodate various use cases, enhancing flexibility in execution strategies.
- Updated DESIGN.md to reflect the new executor modes and their respective use cases, including detailed descriptions and configuration examples.
- Revised TECHNICAL.md to outline the new executor package structure, emphasizing the modular design for future enhancements.
- Enhanced the TODO.md to track the progress of executor mode implementations and related tasks.
- Removed outdated executor stub files and tests, streamlining the codebase for improved maintainability.
- Updated integration tests to utilize the new DryRun executor, ensuring comprehensive coverage of execution scenarios without real agent calls.
2026-01-16 15:27:46 +08:00

234 lines
6.3 KiB
Go

package sandbox
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/yaoapp/yao/agent/robot/executor/types"
robottypes "github.com/yaoapp/yao/agent/robot/types"
)
// Executor implements a sandboxed executor placeholder.
//
// ⚠️ NOT IMPLEMENTED: True sandbox mode requires container-level isolation
// (Docker/gVisor/Firecracker) for security. This placeholder currently
// behaves like DryRun mode and does NOT provide real security isolation.
//
// Future Implementation:
// - Container isolation: Each execution in separate container
// - Resource limits: CPU, memory, disk enforced by container runtime
// - Network isolation: Restricted network via container networking
// - File system isolation: Read-only root, limited writable paths
// - Process isolation: Separate PID namespace
//
// Current behavior: Simulates execution with mock data (same as DryRun)
type Executor struct {
config types.SandboxConfig
execCount atomic.Int32
currentCount atomic.Int32
}
// New creates a new sandbox executor with default settings
func New() *Executor {
return &Executor{
config: types.SandboxConfig{
MaxDuration: 30 * time.Minute,
NetworkAccess: true,
FileAccess: false,
},
}
}
// NewWithConfig creates a sandbox executor with custom configuration
func NewWithConfig(config types.SandboxConfig) *Executor {
return &Executor{
config: config,
}
}
// Execute runs robot execution within sandbox constraints
func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, trigger robottypes.TriggerType, data interface{}) (*robottypes.Execution, error) {
if robot == nil {
return nil, fmt.Errorf("robot cannot be nil")
}
// Create timeout context
execCtx, cancel := context.WithTimeout(ctx.Context, e.config.MaxDuration)
defer cancel()
// Create new context with timeout
sandboxCtx := robottypes.NewContext(execCtx, ctx.Auth)
// Determine starting phase
startPhaseIndex := 0
if trigger == robottypes.TriggerHuman || trigger == robottypes.TriggerEvent {
startPhaseIndex = 1
}
// Create execution record
exec := &robottypes.Execution{
ID: fmt.Sprintf("sandbox_%d", time.Now().UnixNano()),
MemberID: robot.MemberID,
TeamID: robot.TeamID,
TriggerType: trigger,
StartTime: time.Now(),
Status: robottypes.ExecPending,
Phase: robottypes.AllPhases[startPhaseIndex],
Input: types.BuildTriggerInput(trigger, data),
}
// Set robot reference
exec.SetRobot(robot)
// Acquire slot
if !robot.TryAcquireSlot(exec) {
return nil, robottypes.ErrQuotaExceeded
}
defer robot.RemoveExecution(exec.ID)
// Track counts
e.execCount.Add(1)
e.currentCount.Add(1)
defer e.currentCount.Add(-1)
// Update status
exec.Status = robottypes.ExecRunning
// Execute phases with sandbox constraints
phases := robottypes.AllPhases[startPhaseIndex:]
for _, phase := range phases {
// Check timeout
select {
case <-execCtx.Done():
exec.Status = robottypes.ExecFailed
exec.Error = "execution timeout exceeded"
return exec, nil
default:
}
exec.Phase = phase
if e.config.OnPhaseStart != nil {
e.config.OnPhaseStart(phase)
}
// Execute phase with sandbox constraints
if err := e.runSandboxedPhase(sandboxCtx, exec, phase, data); err != nil {
exec.Status = robottypes.ExecFailed
exec.Error = err.Error()
return exec, nil
}
if e.config.OnPhaseEnd != nil {
e.config.OnPhaseEnd(phase)
}
}
// Mark completed
exec.Status = robottypes.ExecCompleted
now := time.Now()
exec.EndTime = &now
return exec, nil
}
// runSandboxedPhase executes a phase with sandbox constraints
func (e *Executor) runSandboxedPhase(ctx *robottypes.Context, exec *robottypes.Execution, phase robottypes.Phase, data interface{}) error {
// Validate agent is allowed (if whitelist is set)
if len(e.config.AllowedAgents) > 0 {
robot := exec.GetRobot()
if robot != nil && robot.Config != nil && robot.Config.Resources != nil {
agentID := robot.Config.Resources.GetPhaseAgent(phase)
if !e.isAgentAllowed(agentID) {
return fmt.Errorf("agent %s is not allowed in sandbox", agentID)
}
}
}
// For now, generate mock output (real implementation would call agents with restrictions)
e.mockPhaseOutput(exec, phase)
return nil
}
// isAgentAllowed checks if an agent is in the whitelist
func (e *Executor) isAgentAllowed(agentID string) bool {
for _, allowed := range e.config.AllowedAgents {
if allowed == agentID || allowed == "*" {
return true
}
}
return false
}
// mockPhaseOutput generates mock output for each phase
func (e *Executor) mockPhaseOutput(exec *robottypes.Execution, phase robottypes.Phase) {
switch phase {
case robottypes.PhaseInspiration:
exec.Inspiration = &robottypes.InspirationReport{
Clock: robottypes.NewClockContext(time.Now(), ""),
Content: "## Sandbox Inspiration\n\nExecuted in isolated sandbox environment.",
}
case robottypes.PhaseGoals:
exec.Goals = &robottypes.Goals{
Content: "## Sandbox Goals\n\n1. [High] Sandboxed goal execution",
}
case robottypes.PhaseTasks:
exec.Tasks = []robottypes.Task{
{
ID: "sandbox-task-1",
GoalRef: "Goal 1",
Source: robottypes.TaskSourceAuto,
ExecutorType: robottypes.ExecutorAssistant,
ExecutorID: "sandbox-agent",
Status: robottypes.TaskPending,
},
}
case robottypes.PhaseRun:
exec.Results = []robottypes.TaskResult{
{
TaskID: "sandbox-task-1",
Success: true,
Output: map[string]interface{}{"mode": "sandbox", "isolated": true},
Duration: 50,
Validation: &robottypes.ValidationResult{
Passed: true,
Score: 1.0,
},
},
}
case robottypes.PhaseDelivery:
exec.Delivery = &robottypes.DeliveryResult{
Type: robottypes.DeliveryNotify,
Success: true,
}
case robottypes.PhaseLearning:
exec.Learning = []robottypes.LearningEntry{
{
Type: robottypes.LearnExecution,
Content: "Sandbox execution completed within constraints",
},
}
}
}
// ExecCount returns total execution count
func (e *Executor) ExecCount() int {
return int(e.execCount.Load())
}
// CurrentCount returns currently running execution count
func (e *Executor) CurrentCount() int {
return int(e.currentCount.Load())
}
// Reset resets the executor counters
func (e *Executor) Reset() {
e.execCount.Store(0)
e.currentCount.Store(0)
}
// Verify Executor implements types.Executor
var _ types.Executor = (*Executor)(nil)