Repository navigation
Conversation
…r tab On a server deployment used from several devices, agents died whenever the user looked away: a phone freezes the page when the screen locks, the 30 s keepalive stops, and the 180 s idle sweep ends every idle agent. Closing a tab on any device also ended agents another device was using. - Idle policy per runtime: desktop unchanged (180 s, no cap); the server keeps idle agents for 2 h, bounded by a cap of 8 idle agents (longest idle and unwatched first). Headless owners (automation, codeg-cli, one-shot) keep the short timeout. New CODEG_ACP_MAX_IDLE_AGENTS. - In web mode closing a view, replacing a preview tab, the frontend idle sweep and the provider unmount detach the view instead of calling acp_disconnect. - A conversation whose agent went away while it was open resumes its session on its own once the page is visible (at most once a minute). - A pending ScheduleWakeup counts as outstanding background work until it fires, is stopped, or passes its deadline plus 5 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@Mistic92 thank you for this, and for the very clear write-up. The problem is real: a phone that locks its screen should not cost the user their agents. The month of logs and the breakdown of who calls We'd like to work through one design question before merging, though. It is about who owns an agent once a view only detaches, and it needs a decision rather than a small patch. There is also one smaller issue in the auto-resume that can be fixed in place. Summary
1. A reopened conversation comes back as a viewerOwnership in codeg is decided on the client. The surface that spawned a connection owns it; another surface that finds a live connection for the same conversation attaches as a viewer. When no local entry can be reused, opening a saved conversation first asks the server for a live connection. If one exists and no local entry owns it, the client takes the viewer path: codeg/src/contexts/acp-connections-context.tsx Lines 7549 to 7563 in ab2e695 With this PR, codeg/src/contexts/acp-connections-context.tsx Lines 7847 to 7862 in ab2e695 So in server mode, after codeg/src/contexts/acp-connections-context.tsx Lines 6907 to 6929 in ab2e695 A viewer is not read-only: it can still send prompts and cancel a turn. But several controls are gated on local ownership, so in that state:
That conflicts with "Explicit user actions are unchanged" in the description. Before this PR, closing an idle owned tab in web mode ended the agent, so reopening spawned a fresh one that this client owned. To be fair, this viewer state was already possible after a page reload, because nothing calls Reproduction (vitest, added to
|
|
codeg work task |
Why
On a server deployment (
codeg-serveron a VM or in Docker, used from several devices) agents keep dying whenever the user looks away from them. Leave the web UI open on a phone, lock the screen for a few minutes, come back, and every conversation's agent has been killed and shows as disconnected. In a desktop browser tab the same agents live for hours.The cause is that agent lifetime is tied to the browser tab:
Connected, nothing in flight) that has not been touched forCODEG_ACP_IDLE_TIMEOUT_SECS, 180 s by default.acp_disconnect. Any device can therefore end an agent another device is using.In a month of logs from one server deployment this sweep ended about 350 agents. Almost all of them were between turns, not mid-turn: a turn in flight was already protected. But the CLI process also holds things a tab cannot see, and they die with it. A pending
ScheduleWakeup(a self-paced/loop) is not counted by the sweep at all.What changes
The server owns the agent; a browser tab is only a view of it.
1. Idle policy per runtime (
acp/idle_sweep.rs,ConnectionManager::sweep_idle_with)codeg-server: idle agents are kept for 2 hours instead of 3 minutes.codeg-cliruns and one-shot generations. No tab ever comes back to them, and they must not hold cap slots.CODEG_ACP_IDLE_TIMEOUT_SECSkeeps its meaning (0disables the sweep); only the server default changes. NewCODEG_ACP_MAX_IDLE_AGENTS(0= no cap)."Put to sleep" is the same disconnect as before: the CLI process ends, the session stays resumable through
session/load.2. Closing a view detaches instead of killing (
serverOwnsAgentLifecycle())In web mode, and in a desktop window bound to a remote server, these paths now only detach the view (new
releaseaction) and never callacp_disconnect:Reopening the conversation attaches to the same running agent. Explicit user actions are unchanged: switching agent or folder, Reconnect, and restart-to-apply after a settings change.
In the local desktop app every path behaves exactly as before.
3. A conversation whose agent went to sleep resumes on its own
When the agent behind the open, active conversation goes away (put to sleep after a long idle stretch, or a server restart), the conversation reconnects and resumes the same session as soon as the page is visible. The user no longer sees a dimmed composer and has to find the Reconnect button first.
This is limited to a connection that went away while the view was open, and to at most once per minute, so an agent that keeps dying cannot become a respawn loop.
4. A pending
ScheduleWakeupkeeps its agent alive (acp/background_watch.rs)The transcript watcher now counts a pending wakeup as outstanding work, from the
ScheduleWakeupack'stoolUseResult.scheduledFor:turnOrigin: "scheduled") or when the loop is stopped (stop: true).Monitoris already covered by the adapter's async-task channel.CronCreateis not handled here: I found no real transcript showing its record shapes, and did not want to guess them.Not in this PR
codeg-server.Tests
idle_sweep: policy defaults and env parsing for both runtimes.manager: the server policy keeps an unwatched agent until its long timeout; headless owners are reclaimed on the short timeout; the cap picks the longest-idle unwatched agents, never touches a watched one, and ignores busy ones.background_watch: a wakeup counts until it fires, a later one replaces it,stopclears it, a long-overdue one is not counted, it expires on its own deadline, and it does not force the 1 s poll.use-connection-lifecycle.unmount.test.ts: server mode releases instead of disconnecting (idle or busy), keeps the view across a transient remount, auto-resumes when the agent goes away, waits for a hidden page to be shown, does not respawn twice within the interval, and never auto-resumes on desktop.acp-connections-context.test.tsx: in server mode the preview release,release(), the frontend idle sweep and the provider unmount detach withoutacp_disconnect.🤖 Generated with Claude Code