Agent Sessions

Long-running AI agent loops with automatic sandbox rotation.

Agent Sessions

Agent sessions let you run autonomous AI coding loops that survive sandbox rotations. The platform manages sandbox lifecycle, checkpoints, and state persistence so your agent can focus on the task.

Concepts

ConceptDescription
**Session**A long-running container for agent work. Owns a sandbox, budget, context, and event log.
**Rotation**When a sandbox nears expiry the platform checkpoints it, creates a fresh sandbox, and restores state — seamlessly.
**Budget**Cost cap (cents), iteration limit, and/or duration limit. Execution is rejected when exceeded.
**Context**Working directory, env vars, and custom JSON state that persist across rotations.
**Event log**Append-only stream of everything that happens: executions, rotations, budget warnings, etc.

Quick Start

Node.js

typescript
import { StateSetSandbox } from '@stateset/sandbox-sdk';

const client = new StateSetSandbox({
  baseUrl: 'https://api.sandbox.stateset.app',
  authToken: process.env.SANDBOX_API_KEY!,
});

// 1. Create a session
const { session } = await client.createAgentSession({
  name: 'refactor-auth',
  budget: { cost_cap_cents: 500, iteration_limit: 100 },
  sandbox: { cpus: '2', memory: '4Gi', timeout_seconds: 3600 },
});

// 2. Start the session (provisions the first sandbox)
await client.startAgentSession(session.id);

// 3. Execute commands (budget-checked automatically)
const result = await client.executeInAgentSession(session.id, {
  command: ['python3', 'analyze.py'],
  working_dir: '/workspace/src',
});
console.log(result.stdout);

// 4. Write files
await client.writeAgentSessionFile(session.id, '/workspace/notes.md', '# Progress\n- Step 1 done');

// 5. Update context (persists across rotations)
await client.updateAgentContext(session.id, {
  working_dir: '/workspace/src',
  custom_state: { step: 1, branch: 'feature/auth' },
});

// 6. Stop when done
await client.stopAgentSession(session.id, 'Task complete');

Python

python
from stateset_sandbox import StateSetSandbox
import os

client = StateSetSandbox(
    base_url="https://api.sandbox.stateset.app",
    auth_token=os.environ["SANDBOX_API_KEY"],
)

# 1. Create a session
session = client.create_agent_session(
    name="refactor-auth",
    budget={"cost_cap_cents": 500, "iteration_limit": 100},
    sandbox={"cpus": "2", "memory": "4Gi", "timeout_seconds": 3600},
)

# 2. Start the session
client.start_agent_session(session.id)

# 3. Execute commands
result = client.execute_in_agent_session(
    session.id,
    command=["python3", "analyze.py"],
    working_dir="/workspace/src",
)
print(result.stdout)

# 4. Write files
client.write_agent_session_file(session.id, "/workspace/notes.md", "# Progress\n- Step 1 done")

# 5. Update context
client.update_agent_context(session.id, {
    "working_dir": "/workspace/src",
    "custom_state": {"step": 1, "branch": "feature/auth"},
})

# 6. Stop when done
client.stop_agent_session(session.id, reason="Task complete")

Budget Configuration

Budgets prevent runaway costs. Set one or more limits when creating a session:

typescript
const { session } = await client.createAgentSession({
  name: 'code-review',
  budget: {
    cost_cap_cents: 1000,          // $10 max
    iteration_limit: 500,           // 500 executions max
    duration_limit_seconds: 7200,   // 2 hours max
  },
});

The platform tracks consumption in session.budget_consumed:

typescript
const { session } = await client.getAgentSession(sessionId);
console.log(`Cost: ${session.budget_consumed.cost_cents}¢`);
console.log(`Iterations: ${session.budget_consumed.iterations}`);
console.log(`Duration: ${session.budget_consumed.duration_seconds}s`);

When a limit is approached (80%) a budget_warning event is emitted. When exceeded, further executions are rejected with budget_exceeded.


Sandbox Rotation

Sessions can outlive individual sandboxes. When a sandbox nears its timeout the platform:

  1. Creates a checkpoint of the current sandbox
  2. Provisions a fresh sandbox with the same config
  3. Restores the checkpoint into the new sandbox
  4. Updates the session's current_sandbox_id

Rotation Config

Control how early rotation begins:

typescript
const { session } = await client.createAgentSession({
  name: 'long-task',
  rotation: {
    pre_rotate_buffer_seconds: 120, // Start rotation 2 min before expiry
  },
  sandbox: { timeout_seconds: 3600 },
});

Detecting Rotations

Poll the event log for rotation events:

typescript
const { events } = await client.getAgentSessionEvents(sessionId, {
  event_type: 'rotation_completed',
});

for (const event of events) {
  console.log(`Rotation at seq ${event.sequence_number}:`, event.payload);
}

Agent Context

Context persists across sandbox rotations. Use it to track working directory, environment, and custom state.

Node.js

typescript
// Set context
await client.updateAgentContext(sessionId, {
  working_dir: '/workspace/project',
  env_vars: { NODE_ENV: 'test' },
  custom_state: {
    current_task: 'implement-login',
    files_modified: ['src/auth.ts', 'src/middleware.ts'],
  },
});

// Read context (part of session)
const { session } = await client.getAgentSession(sessionId);
console.log(session.agent_context.custom_state);

Python

python
# Set context
client.update_agent_context(session_id, {
    "working_dir": "/workspace/project",
    "env_vars": {"NODE_ENV": "test"},
    "custom_state": {
        "current_task": "implement-login",
        "files_modified": ["src/auth.ts", "src/middleware.ts"],
    },
})

# Read context
session = client.get_agent_session(session_id)
print(session.agent_context["custom_state"])

Tool Registry

Register tools so the session tracks which capabilities the agent has:

Node.js

typescript
await client.registerAgentTool(sessionId, {
  name: 'run_tests',
  tool_type: 'custom',
  description: 'Run the project test suite',
  input_schema: {
    type: 'object',
    properties: {
      filter: { type: 'string', description: 'Test name filter' },
    },
  },
});

const { tools } = await client.listAgentTools(sessionId);
console.log(tools.map(t => t.name));

Python

python
client.register_agent_tool(session_id, {
    "name": "run_tests",
    "tool_type": "custom",
    "description": "Run the project test suite",
    "input_schema": {
        "type": "object",
        "properties": {
            "filter": {"type": "string", "description": "Test name filter"},
        },
    },
})

tools = client.list_agent_tools(session_id)
print([t.name for t in tools])

Event Log

The event log is an append-only stream of session activity. Use after_sequence for efficient polling.

Node.js

typescript
let lastSeq = 0;

async function pollEvents(sessionId: string) {
  const { events } = await client.getAgentSessionEvents(sessionId, {
    after_sequence: lastSeq,
    limit: 50,
  });

  for (const event of events) {
    console.log(`[${event.event_type}] seq=${event.sequence_number}`, event.payload);
    lastSeq = event.sequence_number;
  }
}

Python

python
last_seq = 0

def poll_events(session_id: str):
    global last_seq
    events = client.get_agent_session_events(
        session_id,
        after_sequence=last_seq,
        limit=50,
    )
    for event in events:
        print(f"[{event.event_type}] seq={event.sequence_number}", event.payload)
        last_seq = event.sequence_number

Event Types

EventDescription
session_startedSession started, first sandbox provisioned
session_pausedSession paused
session_resumedSession resumed
session_completedSession stopped gracefully
session_failedSession encountered a fatal error
session_cancelledSession force-cancelled
sandbox_createdNew sandbox provisioned
sandbox_terminatedSandbox terminated
rotation_startedSandbox rotation in progress
rotation_completedRotation finished, new sandbox ready
rotation_failedRotation failed
exec_startedCommand execution started
exec_completedCommand execution finished
budget_warningApproaching budget limit (80%)
budget_exceededBudget limit reached
checkpoint_createdCheckpoint saved
checkpoint_restoredCheckpoint restored into sandbox
tool_registeredTool added to registry
heartbeatClient heartbeat received

Session Lifecycle

pending ──▶ running ──▶ completed
              │
              ├──▶ paused ──▶ running
              │
              ├──▶ rotating ──▶ running
              │
              ├──▶ failed
              │
              └──▶ cancelled
  • **pending** — Created but not started.
  • **running** — Active sandbox, accepting executions.
  • **rotating** — Sandbox rotation in progress (brief, automatic).
  • **paused** — Paused by client. Sandbox stays alive but rotation timer stops.
  • **completed** — Stopped gracefully.
  • **failed** — Unrecoverable error (e.g., rotation failure).
  • **cancelled** — Force-terminated by client.

Heartbeat & Reattach

Send periodic heartbeats to prevent idle cleanup and monitor budget:

typescript
const { session } = await client.agentSessionHeartbeat(sessionId);
console.log(`Budget used: ${session.budget_consumed.cost_cents}¢`);

Reconnect after a disconnect using a client ID:

typescript
// On create, provide a client_id
const { session } = await client.createAgentSession({
  name: 'my-agent',
  client_id: 'agent-instance-abc',
});

// Later, reconnect
const { session: existing } = await client.reattachAgentSession('agent-instance-abc');
if (existing) {
  console.log(`Reattached to session ${existing.id}`);
}

Complete Example: Multi-Step Coding Agent

typescript
import { StateSetSandbox } from '@stateset/sandbox-sdk';

const client = new StateSetSandbox({
  baseUrl: 'https://api.sandbox.stateset.app',
  authToken: process.env.SANDBOX_API_KEY!,
});

async function codingAgent(task: string) {
  // Create and start session
  const { session } = await client.createAgentSession({
    name: `coding-${Date.now()}`,
    budget: { cost_cap_cents: 500, iteration_limit: 50 },
    sandbox: { cpus: '2', memory: '4Gi', timeout_seconds: 3600 },
  });
  await client.startAgentSession(session.id);

  try {
    // Step 1: Set up project
    await client.executeInAgentSession(session.id, {
      command: 'git clone https://github.com/example/project.git /workspace/project',
    });
    await client.updateAgentContext(session.id, { working_dir: '/workspace/project' });

    // Step 2: Install dependencies
    await client.executeInAgentSession(session.id, {
      command: ['npm', 'install'],
      working_dir: '/workspace/project',
    });

    // Step 3: Write code
    await client.writeAgentSessionFile(
      session.id,
      '/workspace/project/src/feature.ts',
      `export function newFeature() {\n  return 'implemented';\n}\n`,
    );

    // Step 4: Run tests
    const testResult = await client.executeInAgentSession(session.id, {
      command: ['npm', 'test'],
      working_dir: '/workspace/project',
    });

    // Step 5: Track result in context
    await client.updateAgentContext(session.id, {
      custom_state: {
        task,
        tests_passed: testResult.exit_code === 0,
        output: testResult.stdout.slice(0, 500),
      },
    });

    return { success: testResult.exit_code === 0, output: testResult.stdout };
  } finally {
    await client.stopAgentSession(session.id, 'Agent finished');
  }
}