Skip to content

fix(terminal): persistent sandbox envs survive between turns#6370

Closed
malaiwah wants to merge 1 commit into
NousResearch:mainfrom
malaiwah:upstream-fix/persistent-sandbox-survives-turn
Closed

fix(terminal): persistent sandbox envs survive between turns#6370
malaiwah wants to merge 1 commit into
NousResearch:mainfrom
malaiwah:upstream-fix/persistent-sandbox-survives-turn

Conversation

@malaiwah

@malaiwah malaiwah commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #6369.

Problem

HermesAgentLoop._cleanup_task_resources unconditionally calls cleanup_vm(task_id) at the end of every run_conversation (every user turn), tearing down docker / daytona / modal sandbox containers regardless of persistent_filesystem. This contradicts terminal.lifetime_seconds (the idle reaper) and container_persistent, and causes per-turn loss of /workspace, ~/.config, sub-agent CLI auth state, and any in-sandbox content.

The unconditional teardown was introduced in fbd3a2f ("prevent leakage of morph instances between tasks", 2025-11-04) to plug a Morph backend leak — two days after lifetime_seconds shipped in faecbdd. It was later refactored into _cleanup_task_resources in 70dd3a1 without changing semantics. Code and docs have disagreed since.

Fix

  • Add terminal_tool.is_persistent_env(task_id) — small helper that returns True if the active env has _persistent=True.
  • In _cleanup_task_resources, skip cleanup_vm when the env is persistent. The idle reaper (_cleanup_inactive_envs) still tears it down on terminal.lifetime_seconds expiry.
  • Non-persistent backends (Morph) remain torn down per turn — original leak-prevention intent preserved.

Diff is 36 lines across 2 files.

Verification

Repro from #6369 now shows the same container hostname across turns, and /workspace writes persist until terminal.lifetime_seconds of inactivity.

`_cleanup_task_resources` was unconditionally calling `cleanup_vm()` at
the end of every `run_conversation` (i.e. every user turn), tearing down
the docker/daytona/modal sandbox container regardless of its
`persistent_filesystem` setting. This contradicted the documented intent
of `terminal.lifetime_seconds` (idle reaper) and `container_persistent`,
and caused per-turn loss of `/workspace`, `~/.config`, agent CLI auth
state, and any other content living inside the sandbox.

The unconditional teardown was introduced in fbd3a2f ("prevent leakage
of morph instances between tasks", 2025-11-04) to plug a Morph backend
leak, two days after `lifetime_seconds` shipped in faecbdd. It was
later refactored into `_cleanup_task_resources` in 70dd3a1 without
changing semantics. Code and docs have disagreed since.

Fix: introduce `terminal_tool.is_persistent_env(task_id)` and skip the
per-turn `cleanup_vm` when the active env is persistent. The idle reaper
(`_cleanup_inactive_envs`) still tears persistent envs down once
`terminal.lifetime_seconds` is exceeded. Non-persistent backends (Morph)
are unchanged — still torn down per turn, preserving the original
leak-prevention intent.
@teknium1

teknium1 commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Merged via PR #6412. Your commit was cherry-picked onto current main with your authorship preserved in git log. Great diagnosis and clean fix — the git archaeology was especially thorough. Thanks @malaiwah!

@teknium1 teknium1 closed this Apr 9, 2026
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.

Persistent terminal sandboxes (docker/daytona/modal) are torn down every turn, contradicting terminal.lifetime_seconds

2 participants