Summary
When an incomplete or aborted response is resumed with the Continue/Retry action, the agent can be actively generating messages and executing tools while pi-web reports the session as idle.
Consequences:
- the session tab/drawer has no running spinner;
- the composer running status and Stop action disappear;
GET /api/sessions reports runtime.isRunning: false and runtime.isStreaming: false for the active session.
Reproduction
- Produce an aborted or otherwise incomplete assistant response.
- Use the response card's Continue (or Retry) action, which calls
POST /api/session/retry.
- Let the resumed turn execute one or more tools.
- While tools are visibly running and transcript entries are still being appended, inspect the session tab/composer or
GET /api/sessions.
Actual: the session is shown as idle and the API runtime has isRunning: false.
Expected: the resumed session remains running for the full retry/continuation, including tool execution, and the Stop action remains available.
Root cause
The compatibility fallback in retrySessionFromFailure() calls the low-level agent directly:
await internal.agent.continue();
while (typeof internal._handlePostAgentRun === "function" && await internal._handlePostAgentRun()) {
await internal.agent.continue();
}
This bypasses AgentSession._runAgentPrompt(), which normally sets _isAgentRunActive = true and later emits the settled lifecycle. AgentSession.isStreaming therefore stays false for the entire resumed run.
runtimeForPath() derives isRunning only from live.isStreaming, retrying, or compacting. Although /api/session/retry acquires a work lease, runtimeForPath() does not consider entry.workLeases. Every pi event consequently broadcasts a false session_runtime_changed, immediately overwriting the frontend's optimistic agent_start state.
Relevant locations:
server.ts: retrySessionFromFailure() fallback
server.ts: runtimeForPath()
server.ts: registerLiveSession() runtime broadcast
src/realtime/realtime.ts: session_runtime_changed handling
Suggested direction
Prefer a public AgentSession continuation/retry API that participates in the normal run lifecycle. As a compatibility fallback, ensure the whole low-level continuation is represented as active runtime.
If work leases are incorporated into runtimeForPath(), the finalizer must release the lease before broadcasting the terminal runtime; otherwise the final broadcast will remain running. Concurrency guards should also reject a second retry while a work lease/continuation is active.
Acceptance criteria
- A retried/continued response reports
isRunning: true from start through all model and tool activity.
- The session tab/drawer spinner and composer running/Stop UI stay visible throughout.
- Exactly one terminal runtime update reports the session idle after completion or abort.
- A second retry cannot start while the first retry is active.
- Regression coverage includes a retry with a long-running tool, not only model streaming.
Summary
When an incomplete or aborted response is resumed with the Continue/Retry action, the agent can be actively generating messages and executing tools while pi-web reports the session as idle.
Consequences:
GET /api/sessionsreportsruntime.isRunning: falseandruntime.isStreaming: falsefor the active session.Reproduction
POST /api/session/retry.GET /api/sessions.Actual: the session is shown as idle and the API runtime has
isRunning: false.Expected: the resumed session remains running for the full retry/continuation, including tool execution, and the Stop action remains available.
Root cause
The compatibility fallback in
retrySessionFromFailure()calls the low-level agent directly:This bypasses
AgentSession._runAgentPrompt(), which normally sets_isAgentRunActive = trueand later emits the settled lifecycle.AgentSession.isStreamingtherefore stays false for the entire resumed run.runtimeForPath()derivesisRunningonly fromlive.isStreaming, retrying, or compacting. Although/api/session/retryacquires a work lease,runtimeForPath()does not considerentry.workLeases. Every pi event consequently broadcasts a falsesession_runtime_changed, immediately overwriting the frontend's optimisticagent_startstate.Relevant locations:
server.ts:retrySessionFromFailure()fallbackserver.ts:runtimeForPath()server.ts:registerLiveSession()runtime broadcastsrc/realtime/realtime.ts:session_runtime_changedhandlingSuggested direction
Prefer a public AgentSession continuation/retry API that participates in the normal run lifecycle. As a compatibility fallback, ensure the whole low-level continuation is represented as active runtime.
If work leases are incorporated into
runtimeForPath(), the finalizer must release the lease before broadcasting the terminal runtime; otherwise the final broadcast will remain running. Concurrency guards should also reject a second retry while a work lease/continuation is active.Acceptance criteria
isRunning: truefrom start through all model and tool activity.