Skip to main content

Workspace Persistence

Test file: rossoctl/tests/e2e/openshell/test_10_workspace_persistence.py Tests: 8 | Pass: 7 | Skip: 1 (Kind, fresh cluster)

What This Tests

Validates that PVC-backed workspace state persists across sandbox pod restarts and that builtin sandbox CRs can be created successfully with workspace volumes.

Architecture Under Test

Test Matrix

Testos_genericos_claudeos_opencodeCustom A2A Agents
PVC persistence: session written to PVC
PVC survives sandbox deletion
Sandbox creation: generic
Sandbox creation: Claude
Sandbox creation: OpenCode
Workspace read after pod delete

Note: Custom A2A agents don't use Sandbox CRs (they're deployed as Deployments).

Test Details

PVC Workspace Persistence (ALL builtin sandboxes)

test_pvc_persistence__sandbox__session_written_to_pvc (parametrized: 3 sandbox types)

  • What: Create sandbox with PVC, write session data, verify data persisted
  • Asserts:
    • PVC created
    • Sandbox CR created
    • Pod reaches Running state (within 60s)
    • kubectl exec cat {path} contains expected content
  • Debug points: PVC name, pod name, file path, contents
  • Agent coverage: os_generic, os_claude, os_opencode
  • Skip condition: Base image pull timeout (60s limit)
  • Cleanup: Deletes sandbox + PVC after test

Parametrized test data:

Sandbox TypeContentPathCommand
test-pvc-genericsession-generic/workspace/session.txtecho '{content}' > {path} && sleep 300
test-pvc-claudeclaude-workspace/workspace/.claude/context.jsonmkdir -p /workspace/.claude && echo '{content}' > {path} && sleep 300
test-pvc-opencodeopencode-session/workspace/.opencode/state.jsonmkdir -p /workspace/.opencode && echo '{content}' > {path} && sleep 300

test_pvc_persistence__all__pvc_survives_sandbox_deletion

  • What: PVC persists after Sandbox CR deleted — enables session resume
  • Asserts:
    • PVC created
    • Sandbox created
    • Sandbox deleted
    • PVC still exists (kubectl get pvc succeeds)
  • Debug points: PVC name, sandbox name
  • Agent coverage: ALL builtin sandboxes
  • Cleanup: Deletes sandbox + PVC after test

Builtin Sandbox Creation

test_sandbox_creation__generic__creates_and_runs

  • What: Generic sandbox creates successfully and pod reaches Running state
  • Asserts:
    • Sandbox CR created (kubectl apply succeeds)
    • Pod reaches Running phase (within 60s)
  • Debug points: Sandbox name, pod name, phase
  • Agent coverage: os_generic
  • Skip condition: Base image pull timeout
  • Cleanup: Deletes sandbox after test

test_sandbox_creation__claude__creates_with_workspace

  • What: Claude Code sandbox CR is accepted with PVC workspace mount
  • Asserts:
    • PVC created
    • Sandbox CR created
    • Sandbox CR exists after creation
  • Debug points: Sandbox name, PVC name
  • Agent coverage: os_claude
  • Cleanup: Deletes sandbox + PVC after test

test_sandbox_creation__opencode__creates_with_workspace

  • What: OpenCode sandbox CR is accepted with PVC workspace mount
  • Asserts:
    • PVC created
    • Sandbox CR created
    • Sandbox CR exists after creation
  • Debug points: Sandbox name, PVC name
  • Agent coverage: os_opencode
  • Cleanup: Deletes sandbox + PVC after test

Workspace Read After Restart

test_workspace_read__generic__data_persists_after_pod_delete

  • What: Write to PVC, delete pod, wait for recreate, verify data readable
  • Asserts:
    • PVC created
    • Sandbox created and pod running
    • Data written to /workspace/restart-test.txt
    • Data readable in first pod
    • First pod deleted (kubectl delete pod --force)
    • Second pod created by controller
    • Data readable in second pod (same PVC)
  • Debug points: PVC name, pod1 name, pod2 name, file contents
  • Agent coverage: os_generic
  • Note: Deletes POD (not Sandbox CR) so controller recreates it

Sandbox CR Schema (with PVC)

apiVersion: agents.x-k8s.io/v1alpha1
kind: Sandbox
metadata:
name: test-pvc-claude
namespace: team1
spec:
podTemplate:
spec:
containers:
- name: sandbox
image: ghcr.io/nvidia/openshell-community/sandboxes/base:latest
command: ["sh", "-c", "mkdir -p /workspace/.claude && echo 'session' > /workspace/.claude/context.json && sleep 300"]
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
persistentVolumeClaim:
claimName: test-pvc-claude-pvc

Cleanup Helper

Tests use a cleanup helper to prevent resource leaks:

def _cleanup_sandbox(name: str, pvc: str, ns: str = AGENT_NS):
# Delete Sandbox CR (non-blocking)
kubectl delete sandbox {name} -n {ns} --wait=false

# Force delete any stuck pods
for pod in matching_pods:
kubectl delete pod {pod} --force --grace-period=0

# Wait for pods to terminate
time.sleep(3)

# Delete PVC (only after pods terminated)
kubectl delete pvc {pvc} -n {ns} --wait=false

Why force delete? Sandbox pods may be in CrashLoopBackOff or stuck in Terminating state, blocking PVC deletion.

PVC Access Modes

ModeDescriptionUsed By
ReadWriteOnceSingle node, single writerAll tests (Kind is single-node)
ReadWriteManyMulti-node, multiple writersFuture: shared workspace across pods
ReadOnlyManyMulti-node, read-onlyFuture: skill library volumes

Workspace Directory Structure

Sandbox TypeWorkspace RootSession FilesConfig Files
os_generic/workspace/session.txt
os_claude/workspace/.claude/context.json, .claude/memory/.claude/settings.json
os_opencode/workspace/.opencode/state.json.opencode/config.json

Parametrized Test Data

ALL_SANDBOX_TYPES = [
pytest.param(
"test-pvc-generic",
"session-generic",
"/workspace/session.txt",
"echo 'session-generic' > /workspace/session.txt && sleep 300",
id="generic",
),
pytest.param(
"test-pvc-claude",
"claude-workspace",
"/workspace/.claude/context.json",
"mkdir -p /workspace/.claude && echo 'claude-workspace' > /workspace/.claude/context.json && sleep 300",
id="claude",
),
pytest.param(
"test-pvc-opencode",
"opencode-session",
"/workspace/.opencode/state.json",
"mkdir -p /workspace/.opencode && echo 'opencode-session' > /workspace/.opencode/state.json && sleep 300",
id="opencode",
),
]

Future Expansion

Agent TypeWhen AddedWhat's Needed
Custom A2A agentsPhase 2Add PVC workspace to Deployment spec + Rossoctl backend session store
Shared workspacesPhase 3ReadWriteMany PVC for multi-agent collaboration
Workspace snapshotsPhase 3VolumeSnapshot API for session checkpoints
Workspace templatesPhase 3PVC pre-populated with skills, tools, repos

Common Failure Modes

SymptomCauseFix
Pod not running after 60sImage pull slowIncrease deadline or pre-pull images
PVC stuck in TerminatingPod still boundForce delete pod first
File not found after restartPod recreated WITHOUT PVCVerify volumeMounts in podTemplate
Permission denied writing to /workspacePod securityContext readOnlyRootFilesystemAdd volumeMount for /workspace
Data lost after restartWrong PVC nameVerify same claimName in both Sandbox CRs