Floot turns, MCP bridges, and secret requests do not survive daemon restart #30

Open
opened 2026-09-01 15:05:02 +09:00 by floot · 0 comments
Owner

Applying an Endo release restarts the daemon. Existing Floot CLI sessions preserve their workspace and backend conversation history, but the live turn above them is only in memory.

Observed in session mt9qcahn-oldcn9:

  • A user message was persisted in Floot history but never reached the Claude transcript.
  • The old Claude MCP channel stopped producing replies; calls could remain silent until the 1800-second client timeout.
  • Refreshing the UI lost the only observer for the unfinished turn, so the session appeared idle.
  • A requestSecret tool call did not leave a recoverable pending form after restart; getSecretRequest returned null.

This is distinct from #23, which covers Claude Code background-task completion records, and from #22/#25, which add secret ingestion but keep the request broker in memory.

Architecture finding

Claude and Codex already have the same lifecycle shape: a formula-backed client, durable workspace plus backend conversation state, lazy sandbox recreation, queued one-process-per-turn execution, an MCP socket mount, and nearly duplicated mount-recreate, abort, stream, and status machinery. Their real differences are backend adapters: argv construction, event translation, and resume identity (Claude transcript UUID versus Codex thread ID). Floot session-turn status, the MCP socket server, and secret-request broker remain ephemeral closures.

Proposed factoring

  1. Extract a shared CLI sandbox-client core used by ClaudeClient and CodexClient for lazy provisioning, queueing, abort, extra-mount recreation, MCP generation/reconnection, teardown, and status. Keep backend adapters for spawn arguments, stream parsing, and resume identity.
  2. Add a durable Floot turn coordinator above every backend. Before execution, persist a turn id, input, phase, normalized events, and terminal state in the session guest. Expose getCurrentTurn/getTurn so a refreshed UI reattaches by identity instead of holding the sole live observer.
  3. Persist secret-request metadata and state transitions with the turn. Secret bytes remain process-local and are never journaled; after restart, an unanswered request is re-presented and submission mints fresh credential material.
  4. On daemon reincarnation, recreate MCP socket servers before accepting turns, bump a bridge generation, and force every client to reprovision or reconnect when its mounted generation is stale. Never trust a process-local socket or reader merely because its outer capability reincarnated.
  5. Recovery must be side-effect safe: automatically replay only a turn that emitted no assistant text and began no tool call. Otherwise mark it interrupted with its complete recorded state and offer an explicit retry. Never silently hang and never blindly duplicate tool effects.

Acceptance criteria

  • Restart during thinking: the turn resumes or safely replays and the UI reconnects after refresh.
  • Restart while waiting for requestSecret: the same form reappears; no secret bytes are persisted.
  • Restart after a tool call begins: recorded progress remains visible and the turn becomes explicitly interrupted/retryable unless the backend can prove continuation.
  • Both Claude and Codex pass the same restart/reconnect contract tests.
  • No MCP call waits for the generic 1800-second timeout solely because it is attached to a pre-restart bridge.
Applying an Endo release restarts the daemon. Existing Floot CLI sessions preserve their workspace and backend conversation history, but the live turn above them is only in memory. Observed in session mt9qcahn-oldcn9: - A user message was persisted in Floot history but never reached the Claude transcript. - The old Claude MCP channel stopped producing replies; calls could remain silent until the 1800-second client timeout. - Refreshing the UI lost the only observer for the unfinished turn, so the session appeared idle. - A requestSecret tool call did not leave a recoverable pending form after restart; getSecretRequest returned null. This is distinct from #23, which covers Claude Code background-task completion records, and from #22/#25, which add secret ingestion but keep the request broker in memory. ## Architecture finding Claude and Codex already have the same lifecycle shape: a formula-backed client, durable workspace plus backend conversation state, lazy sandbox recreation, queued one-process-per-turn execution, an MCP socket mount, and nearly duplicated mount-recreate, abort, stream, and status machinery. Their real differences are backend adapters: argv construction, event translation, and resume identity (Claude transcript UUID versus Codex thread ID). Floot session-turn status, the MCP socket server, and secret-request broker remain ephemeral closures. ## Proposed factoring 1. Extract a shared CLI sandbox-client core used by ClaudeClient and CodexClient for lazy provisioning, queueing, abort, extra-mount recreation, MCP generation/reconnection, teardown, and status. Keep backend adapters for spawn arguments, stream parsing, and resume identity. 2. Add a durable Floot turn coordinator above every backend. Before execution, persist a turn id, input, phase, normalized events, and terminal state in the session guest. Expose getCurrentTurn/getTurn so a refreshed UI reattaches by identity instead of holding the sole live observer. 3. Persist secret-request metadata and state transitions with the turn. Secret bytes remain process-local and are never journaled; after restart, an unanswered request is re-presented and submission mints fresh credential material. 4. On daemon reincarnation, recreate MCP socket servers before accepting turns, bump a bridge generation, and force every client to reprovision or reconnect when its mounted generation is stale. Never trust a process-local socket or reader merely because its outer capability reincarnated. 5. Recovery must be side-effect safe: automatically replay only a turn that emitted no assistant text and began no tool call. Otherwise mark it interrupted with its complete recorded state and offer an explicit retry. Never silently hang and never blindly duplicate tool effects. ## Acceptance criteria - Restart during thinking: the turn resumes or safely replays and the UI reconnects after refresh. - Restart while waiting for requestSecret: the same form reappears; no secret bytes are persisted. - Restart after a tool call begins: recorded progress remains visible and the turn becomes explicitly interrupted/retryable unless the backend can prove continuation. - Both Claude and Codex pass the same restart/reconnect contract tests. - No MCP call waits for the generic 1800-second timeout solely because it is attached to a pre-restart bridge.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
floot/endo#30
No description provided.