Skip to main content

View source on GitHub

This example demonstrates how to properly archive/delete OpenHands conversations to release their Persistent Volume Claims (PVCs) and free up storage resources. When you create an OpenHands conversation, the system provisions a sandbox (also called a “runtime”) that includes:
  • Kubernetes Pod - Container running the agent-server
  • Kubernetes Service - Network access to the pod
  • Persistent Volume Claim (PVC) - Storage for the workspace files
  • Ingress/HTTPRoute - External routing
  • ServiceAccount, PodDisruptionBudget - Supporting K8s resources
The PVC persists even when a conversation is paused or stopped, allowing you to resume work later. However, PVCs consume storage quota and incur costs. To release these resources, you need to delete the conversation, which triggers full sandbox cleanup.

How It Works

Based on the runtime-api implementation, here’s what happens when you delete a conversation:

1. Conversation Deletion (DELETE /api/v1/app-conversations/{conversation_id})

The OpenHands Cloud API:
  • Marks the conversation as deleted in the database
  • Checks if any other conversations share the same sandbox
  • If no other conversations use the sandbox, triggers sandbox cleanup

2. Sandbox Cleanup (Runtime API)

The runtime API’s delete_runtime_and_workspace_in_k8s() function removes all Kubernetes resources:
Key Point: The PVC deletion is what actually releases the storage. Once the PVC is deleted:
  • The underlying PersistentVolume (PV) is freed
  • Storage quota is released
  • Associated costs stop accumulating

3. Resource Lifecycle States

* For standard (non-fuse) runtimes, the PVC persists when stopped to allow resuming. A VolumeSnapshot may be taken before deletion for archival purposes.

Prerequisites

Run It

Quick Start: Complete Workflow Demo

Run the complete workflow example to see the entire process:
This will:
  1. Create a new test conversation (provisions sandbox + PVC)
  2. Show conversation details (sandbox_id, status, etc.)
  3. Ask for confirmation to archive
  4. Delete the conversation (releases PVC)
  5. Verify deletion
Perfect for understanding the complete lifecycle!

Force Immediate PVC Cleanup

If you need to release PVCs immediately without waiting for the cleanup cronjob, use the sandbox DELETE endpoint directly:
Key Difference:
  • Conversation DELETE (archive_sandbox.py) - Deletes conversation, cleans up sandbox only if no other conversations use it
  • Sandbox DELETE (force_cleanup.py) - Forces immediate cleanup of all K8s resources including PVC, regardless of conversation state
The sandbox DELETE endpoint directly calls the runtime-api’s stop_runtime() function, which immediately executes delete_runtime_and_workspace_in_k8s(). This bypasses the cleanup cronjob and releases the PVC right away. When to use force cleanup:
  • ✅ You need immediate storage release
  • ✅ You’re cleaning up test/development sandboxes
  • ✅ You’ve verified no important data remains
  • ⚠️ Warning: This deletes the sandbox even if conversations still reference it!

List All Conversations

Output shows conversation details including:
  • Conversation ID
  • Title
  • Sandbox ID (the runtime ID)
  • Status (RUNNING, STOPPED, etc.)
  • Cost metrics

Archive a Specific Conversation

This will:
  1. Show conversation details
  2. Ask for confirmation
  3. Delete the conversation
  4. Trigger sandbox cleanup (if no other conversations use it)
  5. Release the PVC
Example:

Bulk Cleanup Stopped Conversations

This will:
  1. Find all stopped conversations
  2. Show a summary
  3. Ask for confirmation
  4. Archive all stopped conversations
  5. Release their PVCs
This is useful for cleaning up after testing or development.

Important Concepts

Warm Runtimes and PVC Types

OpenHands supports two types of sandboxes:

Standard (PVC-backed)

  • PVC provisioned at startup
  • Workspace stored on persistent disk
  • PVC persists through pause/resume
  • Must delete conversation to release PVC

Fuse/Dormant (S3-backed)

  • No PVC provisioned
  • Workspace stored in S3 via fusey
  • Mounted dynamically at claim time
  • Fast resume without PVC snapshots
  • No PVC cleanup needed - just deletes S3 objects

Cleanup Cronjob

The runtime-api runs a cleanup cronjob (cleanup.py) every 5 minutes that:
  1. Cleanup stuck PVCs - Removes PVCs that never bound
  2. Cleanup terminated pods - Removes pods with no DB record
  3. Pause idle runtimes - Pauses runtimes idle > 30 min (configurable)
  4. Snapshot and delete idle PVCs - For paused standard runtimes
  5. Delete old fuse workspaces - Purges S3 objects for stopped fuse runtimes > 30 days
  6. Delete old runtimes - Removes K8s resources for runtimes stopped > 1 day
Important: The cleanup cronjob does NOT delete PVCs for active or recently stopped conversations. You must explicitly delete the conversation to trigger immediate cleanup.

Files in This Example

  • archive_sandbox.py - Main CLI tool for archiving conversations
  • force_cleanup.py - Force immediate PVC cleanup by deleting sandbox directly
  • example_create_and_archive.py - Complete workflow demonstration
  • requirements.txt - Python dependencies
  • README.md - This documentation

Best Practices

When to Archive

✅ Do archive:
  • Test conversations you no longer need
  • Failed or error conversations
  • Completed one-off tasks
  • Conversations stopped for > 7 days
❌ Don’t archive:
  • Active conversations (status: RUNNING)
  • Conversations you plan to resume soon
  • Conversations with important work not yet backed up

Before Archiving

  1. Download important files from the workspace
  2. Export conversation history if needed (use the download endpoint)
  3. Check for workspace archives - OpenHands may automatically archive workspaces before pause (see tags.archiveworkspacepath)

Workspace Archiving (Automatic)

OpenHands can automatically archive workspace contents before pausing/stopping:
If enabled, the cleanup process will:
  1. Download workspace as tar.gz or git-delta
  2. Upload to S3 with manifest
  3. Tag conversation with archive path
  4. Then delete the PVC
Check conversation.tags.archiveworkspacepath to see if an archive exists.

Monitoring and Verification

Check Kubernetes Resources

If you have kubectl access to the runtime cluster:
After deleting a conversation, these resources should be removed.

Check via API

Example: Full Workflow

Here’s a complete example of creating and archiving a sandbox:

Troubleshooting

”Conversation not found” error

The conversation may already be deleted. This is safe to ignore.

PVC still exists after deletion

Possible reasons:
  1. Another conversation uses the same sandbox - The sandbox is shared
  2. Cleanup not yet run - K8s deletion is asynchronous, may take a few seconds
  3. Kubernetes finalizers - PV/PVC may have finalizers delaying deletion
Check:

Can’t delete running conversation

You can delete running conversations - the API will stop them first. However, it’s cleaner to stop them explicitly:

Summary

Key Takeaways:
  1. ✅ Two ways to release PVCs:
    • Conversation DELETE - Deletes conversation, cleans up sandbox if not shared
    • Sandbox DELETE - Force immediate cleanup, always releases PVC
  2. ✅ No runtime-api access needed - Use enterprise-server API with OH_API_KEY
  3. ✅ Pausing/Stopping preserves the PVC - For resuming work later
  4. ✅ Force cleanup for immediate release - Use force_cleanup.py to bypass cronjob
  5. ✅ Bulk cleanup available - Archive all stopped conversations/sandboxes at once
  6. ✅ Automatic workspace archiving - May preserve files before PVC deletion
  7. ✅ Check before deleting - Download important files first
Quick Decision Guide: When in doubt, remember: DELETE sandbox → Immediate PVC release → Storage freed instantly ✨

APIs Used

Enterprise-Server API (OpenHands Cloud)

You can use these endpoints with just your OH_API_KEY - no direct runtime-api access required: Key Insight: Both conversation DELETE and sandbox DELETE work through the enterprise-server API. You do NOT need direct runtime-api access to force PVC cleanup!

How It Works

When you call DELETE /api/v1/sandboxes/{sandbox_id}:
  1. Enterprise-Server validates your API key
  2. Calls runtime-api’s /stop endpoint internally
  3. Runtime-api executes delete_runtime_and_workspace_in_k8s()
  4. PVC is immediately deleted from Kubernetes

Runtime-API Direct Access (Optional)

If you have direct runtime-api access (e.g., via port-forward for testing), you can also use: However, in production you should use the enterprise-server API (sandbox DELETE) which provides proper authentication, rate limiting, and audit logging.

OpenHands Runtime API

The service managing sandboxes

OpenHands Cloud API Documentation

Full API reference

Kubernetes PVC Documentation

Persistent volume concepts