Skip to content

Add warm-sandbox-pool example - #3

Merged
jpshackelford merged 9 commits into
mainfrom
warm-sandbox-pool
Oct 7, 2026
Merged

jpshackelford merged 9 commits into
mainfrom
warm-sandbox-pool

Conversation

@jpshackelford

@jpshackelford jpshackelford commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Demonstrates maintaining a pool of pre-initialized warm sandboxes, eliminating startup delays by late-binding conversations to prepared sandboxes.

Features:

  • Pre-initialized sandboxes with Ruby/Sinatra already running
  • Automatic pool refill when sandboxes are consumed, with the refill trigger and in-progress count visible in the UI
  • Web UI for monitoring pool status and starting conversations, styled to match the OpenHands Agent Canvas
  • A random access code, printed by the controller, gates every page and API route, so the UI is safe to expose through a sandbox work URL
  • Can be run from an OpenHands sandbox: the controller defaults to port 12000 (a published work port), and an agent only needs HOST=0.0.0.0 to give you a link and the code
  • Unused pool sandboxes are deleted on shutdown, so a run does not leak sandboxes
  • Alternative to custom images using OpenHands Cloud APIs

Validation

  • Full run on a beta OpenHands instance — the pool fills to 3, two consecutive claims attach conversations in about 10-16s, the refill starts when ready drops below 2, and the UI shows the refill and the activity feed.
  • Run from an OpenHands sandbox through a work URL — the page, event stream and claim all work through the proxy; requests without the access code get 401 and a wrong code gets 403.
  • Cleanup — on shutdown the unclaimed pool sandboxes are deleted; the claimed ones and their conversations were removed afterwards, leaving no extra sandboxes.
  • Static checks — ruff, pyright and test_structure.py pass, and CI is green.

Recovered from conversation 4c1947abfc4f4ed6ba76d9b4c7324d8f

Migrated from jpshackelford/oh-examples#17 as part of the move to this repo. Earlier discussion lives on the original PR.
This note was added by an AI agent (OpenHands) on behalf of @jpshackelford.

Demonstrates maintaining a pool of pre-initialized warm sandboxes
for Ruby-based applications, eliminating startup delays by
late-binding conversations to prepared sandboxes.

Features:
- Pre-initialized sandboxes with Ruby/Sinatra already running
- Automatic pool refill when sandboxes are consumed
- Web UI for monitoring pool status and starting conversations
- Alternative to custom images using OpenHands Cloud APIs

Recovered from conversation 4c1947abfc4f4ed6ba76d9b4c7324d8f

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py50100% 
cli.py65395%59–60, 100
client.py1051784%56, 109, 117, 150, 160–162, 173, 203, 206–208, 210, 217–219, 221
metrics.py80298%102, 182
v0.py48394%88, 104, 141
v1.py83989%86, 90, 94, 131, 135, 155, 178, 206, 214
TOTAL3863491% 

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Found by running the recipe end to end against a live OpenHands instance.

Broken as written:
- Init script upload used a non-existent agent-server endpoint
  (/api/upload-files); use /api/file/upload?path=...
- Init script ran apt-get/gem as the non-root sandbox user; use sudo
- Current Sinatra needs rackup and puma to boot

Resource safety:
- Delete a sandbox as soon as provisioning fails
- Stop refilling after MAX_FAILURES consecutive failures (was unbounded)
- Delete unused pool sandboxes on shutdown (SIGTERM/Ctrl-C)
- Bind the UI to 127.0.0.1 by default and drop the open CORS config

Other fixes:
- Upload quote_service.rb instead of a divergent copy embedded in the script
- UI conversation link pointed at localhost instead of OpenHands
- Add INIT_TIMEOUT, MAX_FAILURES, HOST settings that the docs described
- README/QUICKSTART: correct Ruby install method, service access, timings,
  troubleshooting commands and parameter docs
- test_structure.py also syntax-checks the init script and controller

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Docs preview

✅ Docs preview is ready: https://allhandsai-cookbook-preview-pr-3.mintlify.site

Docs PR: OpenHands/docs#911 (draft preview; never merged, closes with this PR).
Rendered from 41d33bb. After merge, a separate sync PR updates the docs.

…claims

Live view (extends the existing page, no new toolchain):
- Controller records an event feed: sandbox created, preparing, ready, pulled
  from the pool, claimed, failed, deleted, miss, halted, shutdown
- Stats strip: claims, average warm-up, average attach time
- "Claimed by conversations" list with per-sandbox warm-up, time waited in
  the pool, attach time and a link to the conversation
- Activity feed panel; the conversation form no longer hides itself when the
  pool is momentarily empty, it shows a refilling notice and disables Start

Fixes found while testing it:
- A sandbox pulled from the pool whose conversation failed to attach was
  stranded as ALLOCATED and never deleted; it is now deleted

Docs accuracy: measured a cold conversation start (12-26s, before any Ruby
install) against attaching to a warm sandbox (~10-12s). The pool removes
sandbox boot + init script from the wait, not the conversation start itself.
Replaced "instant"/"no delay" wording and added a table of what to expect.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands-release-bot Bot added a commit to OpenHands/docs that referenced this pull request Oct 7, 2026
…merge)

Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#3 (Add warm-sandbox-pool example), rendered from OpenHands/enterprise-cookbook@ec179f1.

**Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR.

_Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
Explain how an agent can run the example on a sandbox work port
(HOST=0.0.0.0 PORT=12000), hand the user the work URL to watch the live UI,
and stop the controller afterwards.

Verified through a real work URL: the page, the SSE stream (frames arrive
every ~2s, unbuffered) and POST /api/conversation/start all work via the proxy.

The UI has no login, so the docs carry an explicit security note: anyone with
the URL can start a conversation with an arbitrary prompt on the user's
account. Acceptable for a brief demo with an unguessable URL; not for anything
long-lived.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands-release-bot Bot added a commit to OpenHands/docs that referenced this pull request Oct 7, 2026
…merge)

Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#3 (Add warm-sandbox-pool example), rendered from OpenHands/enterprise-cookbook@ecb63c5.

**Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR.

_Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
…progress

Access code
- The controller generates a random 10-character code at startup (no look-alike
  characters), prints it in its output, and the browser signs in by pasting it.
- Everything except the sign-in page and its static assets returns 401 without
  the session cookie: the dashboard, the status API, the SSE stream and
  POST /api/conversation/start. Comparison is constant-time; the code and the
  session key live only in memory, so a restart invalidates both. An open page
  notices the 401 and falls back to the sign-in page.
- Docs: the sandbox work-URL section now tells the agent to read the code from
  the controller's output and hand the user the link and the code, and the
  security note describes what the code does and does not protect.

Refill visibility
- New line under the stats: "Refilling: N in progress (R ready, target T)", or
  an explanation of why nothing is happening ("2 of 3 ready. A refill starts
  when ready drops below 2.").
- The feed now records why a refill started: "1 ready is below the threshold
  of 2: starting 2 sandbox(es) to get back to 3".
- The claim success box no longer says the pool refills immediately, which was
  not true when ready is still at the threshold.

Verified on beta through a real work URL: unauthenticated requests get 401
(including trailing-slash and double-slash variants), a wrong code gets 403,
a lowercase correct code signs in, claims work through the session, and a
controller restart sends an open page back to sign-in. Test sandboxes and
conversations were deleted.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands-release-bot Bot added a commit to OpenHands/docs that referenced this pull request Oct 7, 2026
…merge)

Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#3 (Add warm-sandbox-pool example), rendered from OpenHands/enterprise-cookbook@85f5a66.

**Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR.

_Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
…l size in the agent steps

Styling
- Restyle the UI with the OpenHands Agent Canvas "DeepSea" dark theme tokens
  (cool-grey scale on #0B0E14, warm gold primary with dark button text, the
  host's success and danger colors), flat 1px borders, no gradients or lift
  animations. Header uses the OpenHands logo and favicon from OpenHands/OpenHands.
- Status badges, info/result boxes and inputs use translucent tints so they
  stay readable on the dark surfaces.

Port
- Default PORT is now 12000, the first port an OpenHands sandbox publishes as a
  work URL, so running from a sandbox needs only HOST=0.0.0.0 and local runs
  use the same number.

Agent instructions
- The sandbox steps now say to use the default pool settings (pool of 3,
  threshold 2). The earlier advice to try POOL_SIZE=1 POOL_THRESHOLD=1 first
  leaves nothing to watch, because a refill only starts once ready drops below
  the threshold; removed from README and QUICKSTART.

Verified on beta through a work URL with the exact documented command
(HOST=0.0.0.0, defaults): sign-in, dashboard, two claims, refill and the
activity feed all render correctly in the new theme.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands-release-bot Bot added a commit to OpenHands/docs that referenced this pull request Oct 7, 2026
…merge)

Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#3 (Add warm-sandbox-pool example), rendered from OpenHands/enterprise-cookbook@154dc5d.

**Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR.

_Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
The message box is now prefilled in the HTML and no longer cleared after a
claim, so each press of Start Conversation sends the example without
retyping. The grey placeholder only shows if the user empties the box.

Verified on beta: two claims in a row, no typing between them.

Co-authored-by: openhands <openhands@all-hands.dev>
openhands-release-bot Bot added a commit to OpenHands/docs that referenced this pull request Oct 7, 2026
…merge)

Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#3 (Add warm-sandbox-pool example), rendered from OpenHands/enterprise-cookbook@41d33bb.

**Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR.

_Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
@jpshackelford
jpshackelford marked this pull request as ready for review October 7, 2026 20:08

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taste: Good - Well-executed cookbook example with thorough documentation and proper validation.

Summary

This PR adds a comprehensive example demonstrating the "warm sandbox pool" pattern for eliminating sandbox startup delays. The implementation is clean, well-documented, and includes proper security controls.

Strengths

  • Complete documentation: Both README.md and QUICKSTART.md are thorough and well-structured
  • Security controls: Proper access code implementation using constant-time comparison, session-based auth with HTTPOnly cookies, and XSS protection via escapeHtml
  • Resource management: Automatic cleanup of unused sandboxes on shutdown, proper thread synchronization with locks
  • Error handling: Comprehensive error handling with circuit breaker pattern (MAX_FAILURES), proper timeout handling, and graceful degradation
  • Validation: Includes test_structure.py for structural validation
  • Production-ready patterns: Demonstrates real-world techniques applicable beyond the demo

Validation Evidence

Per the PR description, this has been validated with:

  • Full run on beta OpenHands instance (pool fills to 3, consecutive claims work, refill triggers correctly)
  • Run from OpenHands sandbox through work URL (all routes work through proxy with access code)
  • Cleanup verified (unused pool sandboxes deleted on shutdown)
  • Static checks pass (ruff, pyright, test_structure.py)
  • CI green

Architecture Review

The implementation correctly:

  • Uses threading with proper locking for concurrent sandbox provisioning
  • Implements SSE for real-time UI updates
  • Handles the full lifecycle: provision → initialize → pool → claim → attach
  • Includes circuit breaker to halt refilling after repeated failures
  • Provides detailed activity logging for debugging

The Ruby service binding to 0.0.0.0 inside the sandbox is correct - it runs in an isolated container and needs to be accessible to the agent.

Repository Fit

This is an excellent addition to the enterprise cookbook:

  • Demonstrates an advanced but practical pattern
  • Well-suited for the examples repository structure
  • Provides clear value for organizations needing fast conversation startup
  • Includes both simple demo (Ruby quotes service) and extensible architecture

[RISK ASSESSMENT]: LOW. All additions to a new subdirectory with self-contained example code. No changes to existing examples or shared infrastructure. Validated working implementation with comprehensive testing.

Verdict: ✅ APPROVE - No material issues found. This is a well-designed, thoroughly documented, and properly validated addition to the cookbook.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/enterprise-cookbook/actions/runs/37679823832

@jpshackelford
jpshackelford merged commit 3f428ac into main Oct 7, 2026
8 checks passed
@jpshackelford
jpshackelford deleted the warm-sandbox-pool branch October 7, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants