Repository navigation
feat: dl <ws> recreate starts the agents it ends again - #675
Conversation
dl <ws> recreate ends every agent in the container. herdr already saves, per pane, the line that starts the pane's agent again. agent_sessions::collect reads it for each pane attached to the workspace, and herdr agent get decides whether the agent in that pane is still live. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
agent_sessions::restart runs herdr pane run with the saved line, and asks again for up to ten seconds while the old transport exits. The pane this dl runs in is handed its line instead of typed into. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
Before the up, the recreate reads which herdr panes hold an agent in the workspace. After the up, it types each saved line back into its pane, and then attaches. Outside herdr, or when herdr does not answer, or when the up fails, it says so, and hands over any line it did not type. None of this refuses the recreate. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…/recreate-keeps-agent
A pane attached to the workspace being recreated can still hold the resume line an earlier `dl other -- claude` saved in it, because a plain attach saves nothing. collect took that line as the pane's session, so the restart started an agent in the other workspace and the notice said the agent here was kept. A saved line now counts only when its workspace word is the id of the workspace being recreated, which is the word dl writes there from the launch and the container hook. A live agent with a line for another workspace is reported as unresumable. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…uild could not read herdr's agent get answer with /result/agent present but no string name read as NoAgent, so collect dropped the pane's saved line. Such an answer now reads as Unanswered, which keeps the line; NoAgent stays for an agent that is absent or null. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…ive agent again When the OS refused to start `devpod up` for a recreate, the old container and its agents were untouched, yet every held pane got a line to run `claude --resume` by hand: a second claude on a session that still ran. A spawn refusal (`NotRun::Blocked`) now says nothing about the held agents. `NotInstalled` and a devpod refusal keep the hand-back, since both can follow a container the recreate already removed. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…panes The CHANGELOG said a recreate outside herdr prints the line to run by hand; there is no line there, and dl says to run 'claude --resume'. docs/cli.md said a pane counts unless herdr reports its agent done; collect also drops a pane where herdr reports no agent. The module doc made the same no-line promise, and a render.rs comment gave the agent notices severities that nothing maps. Docs and comments only: no red test is possible beyond the existing prose guards. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…create's agent restart Pins a pane run refused twice then taken (two ticks of waiting), a saved word with a space or a quote typed back as the same word, a pane herdr will not describe skipped without losing the rest, a live agent with no session file reported unresumable, a stopped workspace's recreate asking herdr nothing, a pane list that fails saying AgentSessionsUnanswered, and the four agent-session notices that had no word test. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…ane gained its agent e0fca22 wrote a word-for-word test of the AgentSessionUnresumable line and a collect test of a live agent with no session file, both against the shape before b47d65d gave the notice an `agent` field and made `HeldSessions.unresumable` a list of `UnresumablePane`. Cherry-picked together, neither test compiled. Name the agent in both. The claude sentence keeps its exact words, and the word test now also pins the sentence a codex pane gets, so the test still says every unresumable line word for word. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
Reviewer's GuideRunning-workspace Sequence diagram for recreating a workspace and restoring agent sessionssequenceDiagram
participant User
participant Launch
participant Herdr
participant Devpod
participant Pane
User->>Launch: run_rebuild(Recreate)
Launch->>Herdr: pane list
Herdr-->>Launch: panes
loop each pane
Launch->>Herdr: process-info(pane)
Herdr-->>Launch: foreground process
Launch->>Herdr: agent get(pane)
Herdr-->>Launch: agent status
Launch->>Launch: read saved_argv(session.json)
end
Launch->>Devpod: bring_up(--recreate)
Devpod-->>Launch: up result
alt up succeeded
loop each held session
Launch->>Herdr: pane run(pane, shell-quoted line)
Herdr-->>Launch: accepted or pane busy
opt pane busy
Launch->>Herdr: pane run retry after 500 ms
end
end
else up failed after starting
Launch-->>User: print saved lines for manual recovery
end
Launch->>User: attach
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is Additional details and impacted files
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Copied from the public-api CI job's diff. public-api.api.txt gains the seven LaunchNotice variants, which is a change to the promised API: a caller that matches LaunchNotice exhaustively needs an arm for each, as for every notice added before them. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
…rt it again herdr's own docs say idle and done both mean the agent is ready for input. done only says nobody has looked at the pane since the turn ended. collect read done as an agent the user had quit, so it dropped the pane's saved line, and the recreate ended that agent for good. Claude-Session: https://claude.ai/code/session_01Ro7QyyAtHmrh9oxyyK6h9m
blooop
left a comment
There was a problem hiding this comment.
This was generated by AI during review.
wf-review of #675 at 6b158582. Fixed point: merge-base with feat/stale-image-ls, e851e750. Range: git diff e851e750...6b158582, which has 10 files, +1804/−7. Two separate subagents ran the two axes in fresh context. Neither saw the code being written.
Standards
Repo rules checked: dash rule, test citations, public-api widening stated in the PR body, no second copy of a fact without a diff test. None of them is broken. Two defects in this code are fixed higher in the stack in #676: Crockford pane ids in saved_argv, and the saved line that a restarted agent lost.
Findings (all non-blocking):
clients/herdr.rs:1810: the doc is wrong. The doc onAgentStatus::Donesays "The agent has ended".agent_sessions.rs:162anddocs/cli.mdsay that herdr'sdonemeans idle and not yet looked at. A maintainer who trusts the enum doc could makecollectdropdonepanes. Fix it before merge.agent_sessions.rs:64-77againstclients/herdr.rs:2009-2031: Duplicated Code.Manager::resolvecopiesResumeReport::resolve: theprovisioning_disabledgate, the socket parent +session.json, and the binary fallback. That is two copies of "one rule for which herdr to ask".agent_sessions.rs:159-174: Primitive Obsession.collectfoldsAgentReadingintoOption<Option<String>>. Match onAgentReadingdirectly.clients/herdr.rs:1803-1858: Speculative Generality. Production code never readsAgentStatusorPaneAgent::status; only a parse test does, and both addpubrows.launch.rs:5220,agent_sessions.rs:246: Duplicated Code. Two call sites build the same shell line fromHeldSession::line. AHeldSession::shell_line()method can own that.launch.rs:5214: bool flag.restart_agent_sessions(.., up: bool)has two behaviours.dl/src/render.rs:3299: string compare. The renderer branches onagent == "claude", butherdr::agent_namedalready models the known agents. The Unseen and Unanswered notices sayclaude --resumein a flow that is generic.agent_sessions.rs:46: Mysterious Name.Managersits besideflows::session_manager. A name likeHerdrPanessays what it does.agent_sessions.rs:182: accidentalpub.agent_readinglands inpublic-api.rest.txt, and onlycollectcalls it.
Axis verdict: Comment. Nothing is blocking.
Spec
Spec: #673 item 2 and its second "Done when" line, and the body of #675.
Met:
- "starts each session again with
claude --resume <id> --remote-control=<name>in the pane it held".restarttypes herdr's saved line back into the same pane (agent_sessions.rs:236), after theupand before the attach (launch.rs:5139-5152). A launch test checks the order. - "this belongs on the host side". All of the work runs in host
dl.
Findings (all non-blocking):
- "records the session id and remote-control name of each Claude process in the container". The PR reads herdr's
session.jsoninstead (agent_sessions.rs:131), and outside herdr it only warns (launch.rs:5176). The PR body gives the reason: argv goes stale after/clear. The deviation is stated. - "
dl <ws> recreateon a workspace with an idle agent leaves the same session resumed, with its transcript, on the new image." The PR tests only with fakes. Note from the run owner: the host run on 2026-10-15 checked this with the whole stack (#676). Two recreates in a row with an idle agent kept the same--resumeid. After the second recreate, the agent answered a question about the conversation from before the first. The evidence is in the "Found on a real host" section of #676. - The PR body says "
herdr agent getremoves a pane whose agent isdoneor absent". The code keepsdone(agent_sessions.rs:164), anddocs/cli.mdagrees with the code. Fix the PR body. - "Saved lines for codex and gemini panes are typed back too" is more than the spec names. It does no harm.
#677 findings 2, 3 and 5 hit this code, and #676 fixes them. Finding 4 (argv[0] is dl) stays unfixed on purpose.
Axis verdict: Comment. Nothing is blocking.
Verdict
Comment. Neither axis has a blocking finding. Before merge, fix Standards 1 (the AgentStatus::Done doc) and Spec 3 (the PR body).
Found in the wf-review of #675 (Standards 1). agent_sessions and docs/cli.md treat done as an idle agent nobody has looked at; the enum doc said the agent had ended, which invites dropping done panes.
…ecreate-keeps-agent
Part of #673 — A workspace keeps its old image after a pull, and a recreate ends its agent. This PR is part 2 of 3:
dl <ws> recreatestarts the agents that it ends again, in the panes that they held.What this changes
flows/agent_sessions.rs, has two steps:collectasks herdr for every pane. It keeps a pane when the pane's foreground is adlsession into this workspace, and herdr'ssession.jsonholds a saved line for the pane that starts an agent. The word afterdlin the saved line must be this workspace id.herdr agent getremoves a pane whose agent is absent. It keeps adonepane, because herdr'sdoneis an idle agent that nobody has looked at.restarttypes each saved line, shell-quoted, into its pane withherdr pane run. herdr refuses a pane until the old session has exited, sorestarttries again every 500 ms, up to 20 times for each pane. It never types into the pane thatdlitself runs in. It returns that pane's line fordlto print.run_rebuildinflows/launch.rsuses these steps forLaunchVerb::Recreateon a running workspace only.resetandcodedo not change.LaunchNoticevariants carry each outcome. Their words are inrust/dl/src/render.rs. No outcome refuses the recreate.herdr agent getparser is now in core (herdr::agent_reading_in,AgentStatus,PaneAgent), so part 3 can read whether an agent is idle.docs/cli.mdhas a new section, "A recreate starts the agents it ends again".CHANGELOG.mdhas an entry.Where this fits
A saved line looks like
dl <ws> -- ... claude ... --remote-control=<ws> --resume <uuid>. The container'sSessionStarthook reports a new id after/clear, so the line holds the session that the pane is in now. The transcripts are in~/.claude, which outlives the container, so--resumefinds the conversation.How I checked it
All tests run on
FakeRunner. This devcontainer has no herdr and no docker daemon.collectbefore theup,restartafter it, and then the attach.dl other -- claudeno longer gets that line on a recreate ofmyws.devpod upnever started tells the user nothing about the agents, because they still run.claude --resume.session.json, a stopped workspace, andpane listfailing.Commands that I ran in the devcontainer:
cargo test --workspace: 3044 passed.cargo clippy --locked --all-targets -- -D warningsandcargo fmt --check: clean.pytest test/against the debug build: 846 passed.prek run --from-ref origin/main --to-ref HEAD: passed.I did not run:
herdr pane runtakes the line as one string and presses Enter, the shape ofagent getfor a pane with no agent, and that a pane falls back to its shell when thedlsession dies.scripts/public-api-snapshots.sh. The tools are not in the container. I copied the diff from thepublic-apiCI job into the snapshot files.Merge danger
Door: two-way. A revert gives back the old recreate. The promised API (
public-api.api.txt) gains sevenLaunchNoticevariants.LaunchNoticeis not#[non_exhaustive], so a caller with an exhaustivematchneeds an arm for each, as for each notice added before.Blast radius: medium. If
collectpicks the wrong pane,dltypes adl <ws> -- claude ...line into a shell prompt where it does not belong. The workspace id check limits this to panes whose saved line names this workspace. If a pane stays busy, the attach waits up to about 10 s for that pane, and up to about 50 s if herdr hangs. The panes run one after another.Review notes
claude --resumepicks a conversation up again. Reading the Claude processes in the container would cover this case, but the session id in a process's arguments is wrong after/clear. Part 3 skips such workspaces, sodl --refresh-stalenever ends an agent that herdr cannot see.up(DevpodNotRun(NotInstalled)),dlcannot tell this from a recreate that removed the container. It then prints the lines to run by hand although the agents may still run. A fix needs a new error variant inworkspace_up.Stack
🤖 Generated with Claude Code
Summary by Sourcery
Restore agents ended by a running workspace recreate in their original panes when possible, while providing clear recovery guidance for sessions that cannot be restarted automatically.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: