Skip to main content

@tokenring-ai/sandbox

Overview

The @tokenring-ai/sandbox package provides an abstract interface for managing sandboxed environments within the Token Ring AI agent system. It enables the creation, execution, and management of isolated containers (e.g., via Docker or similar providers) to safely run commands or code. The package acts as a service layer that abstracts provider-specific details, allowing multiple sandbox providers to be registered and switched dynamically.

The sandbox package integrates seamlessly with the Token Ring agent framework, providing both tool-based interactions and chat commands for interactive control. It leverages a provider-based architecture with the KeyedRegistry pattern for managing multiple sandbox providers.

Key Features

  • Abstract Provider Interface for extensibility (Docker, Kubernetes, etc.)
  • Multi-Provider Support with dynamic switching at runtime
  • Label-Based Container Management for easier referencing
  • Agent State Integration with persistence and transfer capabilities
  • Tool Registration for agent execution via ChatService
  • Chat Command Support for interactive control via AgentCommandService
  • Service Architecture implementing TokenRingService
  • KeyedRegistry Pattern for provider management

Core Components

SandboxProvider Interface

The SandboxProvider interface defines the contract for any concrete sandbox implementation. It defines methods for container lifecycle and execution.

interface SandboxProvider {
createContainer(options?: SandboxOptions): Promise<SandboxResult>;
executeCommand(containerId: string, command: string): Promise<ExecuteResult>;
stopContainer(containerId: string): Promise<void>;
getLogs(containerId: string): Promise<LogsResult>;
removeContainer(containerId: string): Promise<void>;
}

Type Definitions:

InterfaceDescription
SandboxOptionsOptions for container creation including label, image, workingDir, environment, and timeout
SandboxResultResult of container creation with containerId and status
ExecuteResultResult of command execution with stdout, stderr, and exitCode
LogsResultResult of log retrieval with logs string

SandboxService

The SandboxService manages multiple providers and tracks the active container with label-to-container ID mapping. It implements TokenRingService for integration with agents.

Service Name: SandboxService

Description: Abstract interface for sandbox operations

Key Methods:

MethodParametersReturnsDescription
registerProvider(name, resource)name: string, resource: SandboxProvidervoidRegisters a provider in the internal registry
getAvailableProviders()-string[]Returns names of all registered providers
attach(agent)agent: AgentvoidAttaches service to agent and initializes state
requireActiveProvider(agent)agent: AgentSandboxProviderGets active provider or throws error if none set
getActiveProvider(agent)agent: AgentSandboxProvider | nullGets active provider or returns null
setActiveProvider(name, agent)name: string, agent: AgentvoidSets the active provider in agent state
getActiveContainer(agent)agent: Agentstring | nullGets the active container label
setActiveContainer(containerId, agent)containerId: string, agent: AgentvoidSets the active container label in state
createContainer(options, agent)options: SandboxOptions, agent: AgentPromise<SandboxResult>Creates a container using active provider
executeCommand(label, command, agent)label: string, command: string, agent: AgentPromise<ExecuteResult>Executes a command in the specified container
stopContainer(label, agent)label: string, agent: AgentPromise<void>Stops the specified container
getLogs(label, agent)label: string, agent: AgentPromise<LogsResult>Retrieves logs from the specified container
removeContainer(label, agent)label: string, agent: AgentPromise<void>Removes the specified container

SandboxState

The SandboxState class manages agent state for sandbox operations, implementing AgentStateSlice.

State Properties:

PropertyTypeDescription
providerstring | nullCurrent active provider name
activeContainerstring | nullCurrent active container label
labelToContainerIdMap<string, string>Maps labels to container IDs
initialConfigz.output<typeof SandboxAgentConfigSchema>Initial configuration with provider

State Methods:

  • transferStateFromParent(parent: Agent): void - Transfers state from parent agent (for agent teams)
  • serialize(): z.output<typeof serializationSchema> - Serializes state for persistence
  • deserialize(data: z.output<typeof serializationSchema>): void - Deserializes persisted state
  • show(): string[] - Returns state summary strings for display

Services

SandboxService

The SandboxService is the core service implementation that manages sandbox operations. It implements the TokenRingService interface and integrates with the Token Ring agent system.

Service Name: SandboxService

Description: Abstract interface for sandbox operations

Integration: The service is attached to agents via the attach() method, which initializes agent state with the SandboxState class.

Configuration: The service accepts configuration via SandboxServiceConfigSchema which includes provider definitions and agent defaults.

Provider Management:

  • Uses KeyedRegistry for provider registration and lookup
  • Supports multiple providers registered simultaneously
  • Tracks active provider per agent via state
  • Throws error when attempting operations without active provider

Providers

Provider Architecture

The sandbox package uses a provider-based architecture with the KeyedRegistry pattern for managing multiple sandbox providers.

Current Provider:

Provider NameDescriptionPackage
dockerDocker container provider@tokenring-ai/docker

Provider Interface

The SandboxProvider interface defines the contract for all sandbox implementations:

MethodParametersReturnsDescription
createContainer(options)options?: SandboxOptionsPromise<SandboxResult>Creates a new container
executeCommand(containerId, command)containerId: string, command: stringPromise<ExecuteResult>Executes a command in the container
stopContainer(containerId)containerId: stringPromise<void>Stops the container
getLogs(containerId)containerId: stringPromise<LogsResult>Retrieves container logs
removeContainer(containerId)containerId: stringPromise<void>Removes the container

Provider Registration

Providers are registered through the plugin configuration or programmatically via SandboxService.registerProvider().

Plugin Configuration:

app.install(sandboxPlugin, {
sandbox: {
providers: {
docker: {
type: "docker",
// Docker-specific configuration
}
},
agentDefaults: {
provider: "docker"
}
}
});

Programmatic Registration:

import { SandboxService } from "@tokenring-ai/sandbox";
import { DockerSandboxProvider } from "@tokenring-ai/docker";

const sandboxService = new SandboxService({
providers: {},
agentDefaults: { provider: "docker" }
});

sandboxService.registerProvider('docker', new DockerSandboxProvider());

KeyedRegistry Pattern

The KeyedRegistry pattern is used for managing sandbox providers. This pattern provides:

  • Registration: Providers are registered with a unique name via registerProvider()
  • Lookup: Providers can be retrieved by name via internal registry methods
  • Validation: The requireItemByName() method throws an error if the provider doesn't exist
  • Listing: getAllItemNames() returns all registered provider names

RPC Endpoints

The sandbox package does not define any RPC endpoints.

Chat Commands

The package provides the /sandbox command for interactive control in agent chats.

Available Commands

CommandDescription
/sandbox create <label> [image]Create a new container with label and optional image
/sandbox exec <command>Execute command in active container (requires active container)
/sandbox stop [label]Stop container (uses active if unspecified, requires container)
/sandbox logs [label]Get container logs (uses active if unspecified, requires container)
/sandbox remove [label]Remove container (uses active if unspecified, requires container)
/sandbox statusShow active container and provider
/sandbox provider getShow current provider
/sandbox provider set <name>Set provider by name
/sandbox provider resetReset to initial provider
/sandbox provider selectInteractively select provider from available list

Command Usage Examples

/sandbox create myapp ubuntu:22.04
/sandbox exec ls -la /app
/sandbox logs
/sandbox stop
/sandbox status
/sandbox provider set docker
/sandbox provider select

Command Details

/sandbox create <label> [image]

Create a new sandbox container with an optional image.

Example:

/sandbox create myapp
/sandbox create myapp ubuntu:22.04

/sandbox exec <command>

Execute a command in the active container. Requires an active container to exist.

Example:

/sandbox exec ls -la /app

/sandbox stop [label]

Stop a running container. Uses the active container if no label is specified.

Example:

/sandbox stop
/sandbox stop myapp

/sandbox logs [label]

Retrieve logs from a container. Uses the active container if no label is specified.

Example:

/sandbox logs
/sandbox logs myapp

/sandbox remove [label]

Remove a container. Uses the active container if no label is specified.

Example:

/sandbox remove
/sandbox remove myapp

/sandbox status

Show the current sandbox status including active container and provider.

Example:

/sandbox status

/sandbox provider get

Display the currently active sandbox provider.

Example:

/sandbox provider get

/sandbox provider set <name>

Set the active sandbox provider by name.

Example:

/sandbox provider set docker

/sandbox provider reset

Reset the active sandbox provider to the initial configured value.

Example:

/sandbox provider reset

/sandbox provider select

Interactively select the active sandbox provider. Auto-selects if only one provider is configured.

Example:

/sandbox provider select

Configuration

Plugin Configuration Schema

The plugin configuration is defined in schema.ts with the following structure:

const packageConfigSchema = z.object({
sandbox: SandboxServiceConfigSchema
});

const SandboxServiceConfigSchema = z.object({
providers: z.record(z.string(), z.any()).optional(),
agentDefaults: z.object({
provider: z.string()
})
});

const SandboxAgentConfigSchema = z.object({
provider: z.string().optional()
}).default({});

Configuration Options

PropertyTypeRequiredDefaultDescription
providersRecord<string, { type: string }>No{}Provider configurations
agentDefaultsobjectYes-Default provider for agents
agentDefaults.providerstringYes-Required default provider name

Example Configuration

{
"sandbox": {
"providers": {
"docker": {
"type": "docker"
}
},
"agentDefaults": {
"provider": "docker"
}
}
}

SandboxOptions

PropertyTypeRequiredDescription
labelstringNoContainer label for reference
imagestringNoContainer image (e.g., 'ubuntu:latest')
workingDirstringNoWorking directory in container
environmentRecord<string, string>NoEnvironment variables
timeoutnumberNoTimeout in seconds

Integration

The sandbox package integrates with the Token Ring agent system through the following patterns:

  • Service Registration: The plugin registers as a TokenRingPlugin, adding the SandboxService to the application.
  • Chat Commands: The /sandbox command is added to the agent's command list via AgentCommandService.
  • Tool Registration: Tools are registered via the ChatService for agent execution.
  • State Management: The SandboxState slice manages active container and provider state with persistence and transfer capabilities.
  • Agent Integration: Agents can interact with sandbox services via tools and chat commands using the attach() method pattern.

Integration with Agent System

The service integrates with agents through the attach() method, which:

  1. Merges agent-specific configuration with service defaults using deepMerge
  2. Initializes the SandboxState with the agent's configuration
  3. Enables provider selection and container management per agent

Plugin Installation

import TokenRingApp from "@tokenring-ai/app";
import sandboxPlugin from "@tokenring-ai/sandbox";

const app = new TokenRingApp();
app.install(sandboxPlugin, {
sandbox: {
providers: {
docker: { type: "docker" }
},
agentDefaults: {
provider: "docker"
}
}
});

Tools

The package provides the following tools for agent execution:

Tool NameDescriptionInput SchemaReturn Type
sandbox_createContainerCreates a new sandbox container with optional parameterslabel, image, workingDir, environment, timeoutTokenRingToolJSONResult<{ containerId: string; status: string }>
sandbox_executeCommandExecutes a command in a container (uses active if unspecified)label (optional), commandTokenRingToolJSONResult<{ stdout: string; stderr: string; exitCode: number }>
sandbox_stopContainerStops a container (uses active if unspecified)label (optional)TokenRingToolJSONResult<{ success: boolean }>
sandbox_getLogsGets container logs (uses active if unspecified)label (optional)TokenRingToolJSONResult<{ logs: string }>
sandbox_removeContainerRemoves a container (uses active if unspecified)label (optional)TokenRingToolJSONResult<{ success: boolean }>

Tool Input Schemas

sandbox_createContainer:

z.object({
label: z.string().describe("Label for the container"),
image: z.string().optional().describe("Container image to use"),
workingDir: z.string().optional().describe("Working directory in container"),
environment: z.record(z.string(), z.string()).optional().describe("Environment variables"),
timeout: z.number().optional().describe("Timeout in seconds"),
});

sandbox_executeCommand:

z.object({
label: z.string().optional().describe("Container label (uses active container if not specified)"),
command: z.string().min(1).describe("Command to execute"),
});

sandbox_stopContainer:

z.object({
label: z.string().optional().describe("Container label (uses active container if not specified)"),
});

sandbox_getLogs:

z.object({
label: z.string().optional().describe("Container label (uses active container if not specified)"),
});

sandbox_removeContainer:

z.object({
label: z.string().optional().describe("Container label (uses active container if not specified)"),
});

Tool Usage Example

// Agent invokes tool
await agent.executeTool('sandbox_createContainer', {
label: 'myapp',
image: 'node:18'
});
await agent.executeTool('sandbox_executeCommand', {
command: 'node --version'
});

State Management

The SandboxState class manages agent state for sandbox operations, implementing the AgentStateSlice interface.

State Properties

  • provider: string | null - Current active provider name
  • activeContainer: string | null - Current active container label
  • labelToContainerId: Map<string, string> - Maps labels to container IDs
  • initialConfig: z.output<typeof SandboxAgentConfigSchema> - Initial configuration

State Persistence

The state is serialized and deserialized using a Zod schema:

const serializationSchema = z.object({
provider: z.string().nullable(),
activeContainer: z.string().nullable(),
labelToContainerId: z.array(z.tuple([z.string(), z.string()]))
});

Serialization:

serialize(): z.output<typeof serializationSchema> {
return {
provider: this.provider,
activeContainer: this.activeContainer,
labelToContainerId: Array.from(this.labelToContainerId.entries()),
};
}

Deserialization:

deserialize(data: z.output<typeof serializationSchema>): void {
this.provider = data.provider;
this.activeContainer = data.activeContainer;
this.labelToContainerId = new Map(data.labelToContainerId);
}

State Transfer

The transferStateFromParent() method enables state transfer from parent agents, which is useful for agent teams:

transferStateFromParent(parent: Agent): void {
const parentState = parent.getState(SandboxState);
this.provider = parentState.provider;
this.activeContainer = parentState.activeContainer;
this.labelToContainerId = new Map(parentState.labelToContainerId);
}

State Display

The show() method returns a human-readable state summary:

show(): string[] {
return [
`Active Provider: ${this.provider}`,
`Active Container: ${this.activeContainer ?? "(none)"}`,
];
}

Usage Examples

1. Plugin Registration

The package is designed as a Token Ring plugin. It automatically registers tools and chat commands when installed:

import TokenRingApp from "@tokenring-ai/app";
import sandboxPlugin from "@tokenring-ai/sandbox";

const app = new TokenRingApp();
app.install(sandboxPlugin, {
sandbox: {
providers: {
docker: { type: "docker" }
},
agentDefaults: {
provider: "docker"
}
}
});

2. Using the Service Directly

import { SandboxService } from "@tokenring-ai/sandbox";
import { DockerSandboxProvider } from "@tokenring-ai/docker";

const sandboxService = new SandboxService({
providers: {},
agentDefaults: { provider: "docker" }
});

// Register a provider
sandboxService.registerProvider('docker', new DockerSandboxProvider());

// Create and use container
const result = await sandboxService.createContainer({
label: 'myapp',
image: 'ubuntu:latest'
}, agent);
console.log(`Created: ${result.containerId}`);

const execResult = await sandboxService.executeCommand(
result.containerId,
'ls -la',
agent
);
console.log(`Stdout: ${execResult.stdout}`);

3. Provider Switching

// List available providers
const providers = sandboxService.getAvailableProviders();

// Set active provider
sandboxService.setActiveProvider('docker', agent);

// Create container with active provider
const container = await sandboxService.createContainer({ label: 'test' }, agent);

4. Label-Based Container Management

// Create container with label
await sandboxService.createContainer({ label: 'myapp' }, agent);

// Execute command using label
await sandboxService.executeCommand('myapp', 'ls -la', agent);

// Stop container using label
await sandboxService.stopContainer('myapp', agent);

Error Handling

The package throws errors in various scenarios:

  • No Active Provider: When attempting to perform operations without an active provider set

    [SandboxService] No active provider set
  • No Container Specified: When a tool or command requires a container but none is specified and no active container exists

    [sandbox_executeCommand] No container specified and no active container
  • Command Failed: When a command executes with a non-zero exit code, the tool returns the exit code but does not throw. The error is logged via agent.errorMessage().

  • Provider Not Found: When attempting to set a provider that is not registered

    Provider "docker" not found. Available providers: kubernetes
  • No Initial Provider: When attempting to reset to an initial provider that was not configured

    No initial provider configured
  • Command Failed (Chat Command): Chat commands throw CommandFailedError with usage information when required arguments are missing.

Best Practices

  • Label Management: Use descriptive labels for containers to make referencing easier
  • Provider Selection: Set the active provider before creating containers
  • State Persistence: Leverage agent state management for maintaining container references across sessions
  • Error Handling: Always check for active containers before executing commands
  • Resource Cleanup: Use removeContainer to clean up containers when finished
  • Label Uniqueness: Ensure labels are unique to avoid conflicts in the label-to-container mapping
  • Provider Configuration: Always configure a default provider in agentDefaults to avoid "no active provider" errors

Testing and Development

Building

bun run build

Testing

bun run test

Testing with Coverage

bun run test:coverage

Testing with Watch Mode

bun run test:watch

Extending

To add new sandbox providers:

  1. Create a class that implements SandboxProvider interface
  2. Implement all required methods
  3. Register the provider with SandboxService.registerProvider()
  4. Add provider type handling in plugin.ts if using plugin registration

Example:

import { SandboxProvider, SandboxOptions, SandboxResult, ExecuteResult, LogsResult } from "@tokenring-ai/sandbox";

class MyCustomProvider implements SandboxProvider {
async createContainer(options?: SandboxOptions): Promise<SandboxResult> {
// Implementation
return { containerId: 'custom-id', status: 'running' };
}

async executeCommand(containerId: string, command: string): Promise<ExecuteResult> {
// Implementation
return { stdout: '', stderr: '', exitCode: 0 };
}

async stopContainer(containerId: string): Promise<void> {
// Implementation
}

async getLogs(containerId: string): Promise<LogsResult> {
// Implementation
return { logs: '' };
}

async removeContainer(containerId: string): Promise<void> {
// Implementation
}
}

Dependencies

Production Dependencies

PackageVersionDescription
@tokenring-ai/app0.2.0Base application framework
@tokenring-ai/chat0.2.0Chat service integration
@tokenring-ai/docker0.2.0Docker provider implementation
@tokenring-ai/agent0.2.0Agent system integration
@tokenring-ai/utility0.2.0Utility functions
zod^4.3.6Schema validation

Development Dependencies

PackageVersionDescription
vitest^4.1.0Testing framework
typescript^5.9.3Type checking
  • @tokenring-ai/docker: Docker provider implementation for sandbox
  • @tokenring-ai/agent: Agent system for integration
  • @tokenring-ai/chat: Chat service for tool and command integration
  • @tokenring-ai/app: Application framework for plugin registration

License

MIT License - see LICENSE file for details.

Copyright (c) 2025 Mark Dierolf