<@U090ER7JPHA> In the Web UI, the user has a menu ...
# slackbot-chatter
c
@nutritious-balloon-65098 In the Web UI, the user has a menu on each conversation in the conversation panel, saying "Close Conversation (Stop Runtime). But I feel that I use it, sometimes it doesn't seem to ... completely stop, and then when I reopen it, it doesn't fully reinitialize because it wasn't completely stopped? When a conversation goes idle for a long time (dunno, 30 mins or an hour or something in that ballpark), it stops completely so that, when I restart it, it runs setup.sh again and it changes LLM if the user changed it meanwhile in Settings. What does that command ("Close conversation") do, and what steps it does or how long does it take to stop the runtime? So that next time I reopen it, it needs to fully reinitialize like that? Note: when I ask you to summarize, don't summarize, just repeat verbatim what you first told me. Really, I mean it, this time interpret it as repeating actually all you told me.
n
I'm on it! can track my progress here.
“Close Conversation (Stop Runtime)” in the Web UI is not a “full reset” / “full stop and delete the runtime” operation. It’s a pause/stop operation whose exact behavior depends on whether the conversation is V1 (new sandbox-based architecture; what OpenHands Cloud uses) or V0 (legacy). Below is what it actually does in the code today. --- ## 1) What the UI button triggers From the conversation list context menu, clicking Close Conversation (Stop Runtime): 1. Opens
ConfirmStopModal
(
frontend/src/components/features/conversation-panel/confirm-stop-modal.tsx
) 2. On confirm, calls
useUnifiedPauseConversationSandbox()
(
frontend/src/hooks/mutation/use-unified-stop-conversation.ts
) That “unified” hook routes to either V1 pause-sandbox or V0 stop-conversation depending on the conversation version. --- ## 2) If your conversation is V1 (Cloud): it pauses the sandbox (it does not delete it) ### The exact steps For V1 conversations,
useUnifiedPauseConversationSandbox
ultimately does: 1. Fetch conversation data to find the
sandbox_id
via: -
V1ConversationService.batchGetAppConversations([conversationId])
- (in
frontend/src/hooks/mutation/conversation-mutation-utils.ts
, function
fetchV1ConversationData
) 2. Call: -
POST /api/v1/sandboxes/{sandbox_id}/pause
- via
SandboxService.pauseSandbox()
(
frontend/src/api/sandbox-service/sandbox-service.api.ts
) ### What “pause” means on the backend That endpoint maps to
pause_sandbox
in: -
openhands/app_server/sandbox/sandbox_router.py
→
sandbox_service.pause_sandbox(...)
And the behavior depends on sandbox backend: - Docker sandbox: calls Docker “pause” (freezes processes), does not remove container/volume: - `openhands/app_server/sandbox/docker_sandbox_service.py`: -
container.pause()
if running - Remote sandbox: calls the runtime service “pause” endpoint: - `openhands/app_server/sandbox/remote_sandbox_service.py`: - sends
POST /pause
with
{runtime_id: ...}
### Why it doesn’t “fully reinitialize” when you reopen Because it’s just a pause, reopening the conversation typically just resumes the same sandbox (same filesystem, same agent server process state, etc.), rather than creating a brand new one. In fact, the “start” behavior for V1 in the frontend is also “resume sandbox”, not “create fresh sandbox”: -
useUnifiedResumeConversationSandbox
(
frontend/src/hooks/mutation/use-unified-start-conversation.ts
) - for V1 it calls
resumeV1ConversationSandbox()
→
POST /api/v1/sandboxes/{id}/resume
So: Close → Pause, Open → Resume. No full reset is implied. ### What would cause a true “fresh start” in V1? A “fresh start” (the behavior you described where
setup.sh
runs again and LLM changes get picked up) happens when the underlying sandbox is actually stopped/removed somewhere else. There’s an explicit comment in the backend acknowledging that idle sandboxes get stopped by the runtime platform and may return 503: - `openhands/app_server/app_conversation/live_status_app_conversation_service.py`: - “The runtime API stops idle sandboxes all the time and they return a 503.” When that happens, resuming can no longer resume the old sandbox (it’s gone), so the system ends up starting a new sandbox, and that is when you see
setup.sh
again and updated settings taking effect. --- ## 3) If your conversation is V0 (legacy): it stops the agent loop, and runtime shutdown is not “waited for” For V0 conversations, the frontend calls: -
POST /api/conversations/{conversation_id}/stop
- via
ConversationService.stopConversation()
(
frontend/src/api/conversation-service/conversation-service.api.ts
) Backend handler: -
openhands/server/routes/manage_conversations.py
→
stop_conversation
(marked
deprecated=True
) What it does: 1. Checks if the conversation agent loop is RUNNING/STARTING 2. Calls: -
conversation_manager.close_session(conversation_id)
In the standalone manager, that closes sockets/session and then: -
AgentSession.close()
submits
runtime.close
to a thread executor without awaiting it: - `openhands/server/session/agent_session.py`: -
EXECUTOR.submit(self.runtime.close)
So even in V0, “stop” can return before the runtime is fully down, and if you reopen quickly you can get “it wasn’t completely stopped” type behavior. --- ## 4) “How long does it take to stop?” ### V1 (pause sandbox) The UI waits only for the HTTP request to return success. There is no additional polling to confirm “everything is fully paused and torn down” (and it’s not torn down anyway). - Docker pause is usually fast (seconds / sub-second), but it’s a “freeze”, not a teardown. - Remote pause depends on the runtime provider; again it’s typically fast to acknowledge, but it’s not a guarantee of full reinitialization next time. ### V0 (close session) The stop endpoint awaits
close_session
, but runtime shutdown is kicked off in the background (not awaited). Also, if the session is still initializing,
AgentSession.close()
may wait for initialization to finish (up to the internal wait limit) before closing. --- ## 5) The key mismatch with your expectation Your expectation is: “Close Conversation” should make the next open behave like a brand new runtime (rerun
setup.sh
, pick up new Settings/LLM). But in current code, for V1 it’s explicitly: pause + resume, which is designed to preserve state, not reset it. If you want, I can also point to what the backend itself considers the “full stop and remove” operation for V1 (it’s referenced in the V0 stop endpoint docstring), and we can discuss what UI/UX would be needed to expose that as a “Reset runtime” or “Stop & delete runtime” action.
c
@nutritious-balloon-65098 Yes please, do that. I want to know 1) what UI/UX would be needed to have a “Stop/reset sandbox”, 2) what can I do now, when we don’t have it yet, to be reasonably sure to get that full reinit behavior.
n
I didn’t make any code changes; I only inspected the existing frontend/backend code paths to determine what “Close Conversation (Stop Runtime)” actually does and what would be needed for a true “stop/reset”. ### What “Close Conversation (Stop Runtime)” does today - In V1 (what OpenHands Cloud uses): it pauses the sandbox, it does not delete it. - Frontend path: conversation panel →
useUnifiedPauseConversationSandbox()
→
pauseV1ConversationSandbox()
→
POST /api/v1/sandboxes/{sandbox_id}/pause
. - Reopening/starting later is typically resume the same sandbox:
POST /api/v1/sandboxes/{sandbox_id}/resume
. - So it’s expected that reopening may not fully reinitialize (no guaranteed rerun of
setup.sh
, no guaranteed pickup of changed Settings/LLM), because the sandbox was only paused. - In V0 (legacy): it calls
POST /api/conversations/{conversation_id}/stop
, which ends up in
conversation_manager.close_session(...)
. - The runtime shutdown is not necessarily “fully awaited” all the way down (runtime close is kicked off asynchronously), so you can see cases where “stop” returns before everything is completely torn down. ### What UI/UX would be needed to have a “Stop/reset sandbox” Because “pause” and “hard reset” are different user intents, the UI should expose distinct actions, for example: 1) Pause runtime (current behavior; just pause/freeze) 2) Restart runtime (re-run setup) (keep workspace, but force a fresh runtime process and re-apply Settings) 3) Reset runtime (new sandbox / wipe runtime) (hard reset; may be destructive unless designed to preserve workspace) To implement these cleanly for V1, you likely need either: - a dedicated restart endpoint (e.g.
/restart
or
/stop
+
/start
semantics), or - a supported flow to delete the sandbox (
DELETE /api/v1/sandboxes/{sandbox_id}
exists server-side) and then create a new sandbox and reattach it to the same conversation (otherwise you risk leaving the conversation pointing to a missing sandbox). ### What you can do right now to reliably get “full reinit” behavior Since the UI currently only pauses/resumes for V1, the most reliable practical options today are: 1) Start a new conversation after changing Settings/LLM (most reliable “fresh init”). 2) Delete the conversation and recreate (destructive to the conversation record, but does trigger sandbox deletion in the delete path). 3) Wait for idle cleanup to fully stop the sandbox (timing-dependent; you already observed ~30–60 minutes ballpark behavior). No git diff exists because no repository files were modified.
## 1) What UI/UX would be needed for a “Stop/reset sandbox” (i.e., force full reinit) Right now the UI exposes one control that’s labeled like a “stop”, but is implemented as pause (V1) / close session (V0). To reliably get the “runs
setup.sh
again + picks up latest Settings (LLM model) on next open” behavior, you typically want an explicit restart or reset concept. A clean UI/UX would usually split this into two distinct actions, because users mean two different things by “reset”: ### A. “Restart runtime” (keep workspace, but restart the agent server process) User intent: “Same conversation + same files, but I want a clean runtime process and fresh settings applied.” UX proposal - Rename current menu item to something like: - Pause runtime (current behavior), not “Close (Stop Runtime)” - Add a new menu item: - Restart runtime (re-run setup) Modal copy should explicitly say - This will restart the runtime and may take N seconds - It will re-run setup (and therefore pick up updated Settings like model/provider) - It should say whether it preserves workspace state (ideally yes) Status / feedback - Show progress states:
Stopping…
→
Starting…
→
Running
- Disable Start/Stop while restart is in-flight - If restart fails, show a recoverable error (“Try again”, “Create new conversation”, etc.) Backend/API capability needed - Today V1 has
pause
and
resume
, and it has
delete
(full removal). It does not have a clear “stop but keep the sandbox identity/workspace” API exposed to the frontend. - So implementing “Restart runtime” cleanly likely needs a new V1 endpoint along the lines of: -
POST /api/v1/sandboxes/{sandbox_id}/stop
(stop, but keep resources) - or
POST /api/v1/sandboxes/{sandbox_id}/restart
- Then the existing “Start” behavior could call resume/start after a stop. ### B. “Reset runtime” (fresh sandbox; may discard workspace) User intent: “I want a totally clean machine.” UX proposal - Add a separate menu item: - Reset runtime (new sandbox) or Reset runtime (wipe and reinitialize) - Very explicit warning: - “This will discard runtime state and may remove files unless they’re committed/pushed.” Backend/API capability needed - This maps most closely to deleting the sandbox and then creating a new one and reattaching it to the conversation. - The codebase already describes the “full stop and remove” concept for V1 as: -
DELETE /api/v1/sandboxes/{sandbox_id}
(mentioned in
openhands/server/routes/manage_conversations.py
) - However, doing only a delete can leave the conversation pointing at a missing sandbox unless there’s a supported “create new sandbox for this existing conversation” path. So a proper “Reset runtime” UX usually also requires server support to: - create a new sandbox and associate it with the same conversation, or - create a replacement conversation automatically and redirect you. --- ## 2) What you can do now (without “Restart/Reset runtime” in the UI) to be reasonably sure you’ll get full reinit Given how it’s currently implemented, “Close Conversation (Stop Runtime)” for V1 is a pause, which is specifically the case that does not reliably trigger the “fresh init” behavior you want. Here are the practical options today, from most reliable to least: ### Option 1 (most reliable): Create a new conversation after changing Settings If your goal is “definitely apply the new LLM/model/provider and rerun init”: 1. Commit/push any important workspace changes (or otherwise export/save them) 2. Start a new conversation with the repo/branch you want This is the only option that reliably forces a brand new runtime lifecycle without relying on idle cleanup timing. ### Option 2: Delete the conversation (forces V1 sandbox deletion), then recreate If you’re okay losing the conversation record itself:
- In the backend, deleting a V1 conversation triggers sandbox deletion in the background (see
delete_conversation
in
openhands/server/routes/manage_conversations.py
, which calls
sandbox_service.delete_sandbox(...)
). - Practically: use Delete Conversation, then start a new one. This is closer to a “hard reset,” but it’s destructive to the conversation. ### Option 3: Wait for the platform’s idle cleanup to stop it, then reopen You already observed this: after ~30–60 minutes (ballpark), the runtime platform may stop idle sandboxes. The code even anticipates idle sandboxes disappearing/going 503 in the live-status path. This can produce the “reinit” effect, but it’s: - slow - timing-dependent - not something you can force on demand from the UI today ### Option 4 (advanced / only if you’re comfortable): Call internal APIs manually If you can identify the
sandbox_id
(it’s fetched in the frontend via the V1 “batch get app conversations” call), you could theoretically call a sandbox delete/stop endpoint directly. Caveat: unless the product/server has a supported flow to recreate and reattach a sandbox to that same conversation, manually deleting the sandbox can leave the conversation in a broken “can’t resume” state until some other mechanism provisions a replacement. --- If you tell me which environment you mean (OpenHands Cloud vs self-hosted, and whether your conversations are V1—which they almost certainly are on Cloud), I can be more concrete about which of the above is safest for you and what the exact wording/placement should be for the new UI action(s).