9.8 KiB
Session Resumption LLD
Goals
- Support multiple active sessions per task.
- Resume sessions after backend restart without relying on user navigation.
- Distinguish resumable vs non-resumable agents (no
resume_token). - Keep session state consistent across backend, agentctl, and UI.
- Support local and remote executors.
Key Assumptions
- A session is the unit of execution; each session has at most one active agent execution.
- Task can have multiple sessions concurrently (active or historical).
- Agents that support session resumption provide a
resume_tokenin session metadata. - Agents that do not support resumption cannot resume after restart.
- Remote executors keep running across backend restarts; the backend must reconnect.
Data Model (existing + additions)
task_sessionsid,task_id,state,agent_profile_id,agent_execution_id,metadata(includesresume_token)worktree_id,worktree_path,worktree_branchexecutor_id(local/remote)
executors_running(persistent table)id(executor instance id),session_id,task_id,runtimeagentctl_url,agentctl_port,container_id(docker),pid(local)status:starting | ready | error | stoppedsupports_session_resume,supports_workspace_onlyresume_tokenworkspace_path,worktree_id,worktree_path,worktree_branchlast_seen_at,error_message
executors_cache(in-memory only)execution_id,session_id,runtime,status,agentctl_url/port,workspace_path
Note: agentctl state is derived from executors_running.status and health checks.
Agent Capability
- Add
supports_session_resumeboolean to agent registry or agent profile. - When
false, mark session as non-resumable on backend restart. - UI should show: “This agent cannot resume previous sessions. Create a new session.”
Executor Types
- Local Docker: containers survive backend restart; recover via Docker APIs.
- Local Standalone: agentctl processes are local and typically do not survive restart.
- Remote Executor: agentctl runs remotely; survives backend restart; backend must reconnect.
Startup Flow (Backend)
Goal: recover agentctl instances from executors_running on backend start, not when a user opens a session.
-
Load executors
- Query
executors_runningwherestatusin (starting,ready).
- Query
-
Reject non-resumable executors
- If
supports_session_resume=false, mark related sessionsERRORand seterror_message=not_resumable.
- If
-
Resume executors by runtime
- Local Docker:
- Verify Docker daemon running.
- Locate containers by
executors_running.container_idor session label. - If container missing: mark executor
error, mark sessionsERROR. - If container running: healthcheck agentctl.
- Local Standalone:
- Verify worktree path exists.
- If missing: mark executor
error, mark sessionsERROR. - Start agentctl process and healthcheck.
- Remote Executor:
- Call
Health()onagentctl_url. - If unreachable: mark executor
error, mark sessionsERROR.
- Call
- Local Docker:
-
Sync executor cache
- Register running executions in
executors_cachekeyed bysession_id. - Update
executors_running.last_seen_at.
- Register running executions in
-
Enable workspace access
- If agentctl is healthy, set
executors_running.status=ready. - Shell/fs/git are enabled regardless of agent resume.
- If agentctl is healthy, set
-
Resume agent session
- If
supports_session_resume=trueandresume_tokenpresent, resume agent. - On success: set
session.state = WAITING_FOR_INPUT. - On failure: set
session.state = ERROR, updateerror_message.
- If
-
Publish updates
- Emit session agentctl events to UI.
Executor Resumption Capability
Executors should report whether agent session resume is supported by the runtime + agent, so the backend can decouple executor lifecycle from agent lifecycle.
Agentctl Capability Probe
After agentctl is reachable, call a capability endpoint (new or existing) to determine:
supports_session_resume: whether the agent can resume with the session resumption token.supports_workspace_only: whether workspace-only mode is available (for git/fs/shell without agent).
Note: requires_new_session is redundant with supports_session_resume=false. If we keep it, it must be a derived/alias field, not an independent signal.
This allows:\n
- Executor resume (agentctl + workspace streams) even if agent resume is not possible.
- The UI to enable shell/git/files regardless of agent resumption outcome.
Session Resumption Flow (Backend)
Triggered when user explicitly requests resume.
-
Validate session:
- Session exists and belongs to task.
- Agent profile supports resume and session has
resume_token. - Worktree exists if required.
-
Create agentctl instance (local only):
- Call
LaunchAgentwithsession_id,worktree_path,worktree_branch. - Persist
executors_running.status=starting.
- Call
-
Start agent process and resume:
- Resume agent session with
resume_token. - On success:
session.state = WAITING_FOR_INPUTexecutors_running.status = readytask.state = REVIEW
- On failure:
session.state = ERRORexecutors_running.status = error- Persist
error_message.
- Resume agent session with
Session Status API
task.session.status
Return session-specific status (no task-level fallback):
session_id,task_id,state,agent_profile_idexecutor_status,agent_execution_id,runtimeis_agent_running(based on execution for that session)is_resumable(resume supported +resume_tokenpresent)needs_resume(no running execution + resumable)resume_reason(e.g.,agent_not_running,not_resumable,missing_resume_token)worktree_path,worktree_branch
WebSocket Events
Session-scoped events:
session.agentctl_startingsession.agentctl_readysession.agentctl_errorsession.state_changedsession.message.added,session.message.updated
Payloads must include session_id and task_id.
Frontend Flow
- SSR page loads session + task + messages and hydrates store.
- useSessionResumption
- Calls
task.session.status. - If
needs_resume && is_resumable: show “Resume session” CTA or auto-resume (configurable). - If
!is_resumable: show “Create new session” CTA.
- Calls
- UI states
executor_status=starting: show spinner and disable shell/file panels.executor_status=ready: allow shell/file access.executor_status=error: show error badge and retry/resume action.
Multi-Session per Task Considerations
- Executor and lifecycle manager must key executions by
session_id(done). task.session.statusmust only checkexecution.SessionID == sessionID.- WebSocket subscriptions should be per-session to avoid cross-session updates.
Non-Resumable Agents
- Add
supports_session_resume=falsein agent registry/profile. - Backend returns
is_resumable=falsein status. - UI displays a banner: “This agent doesn’t support resuming. Start a new session.”
Open Decisions
- Whether to keep
executors_runninglean or store full runtime metadata. - Auto-resume on backend startup vs on-demand (current request: startup).
- Whether to auto-resume all resumable sessions or only those in RUNNING/WAITING_FOR_INPUT.
- Remote executor reconnect timeout/backoff policy.
Resumption Flow Diagrams
Local Docker Executor (agentctl container survives)
Backend Restart
|
|-- load executors_running (starting/ready)
|-- docker: RecoverInstances()
| -> map container to executors_running.id or session_id label
|-- agentctl Health()
|-- agentctl Capabilities? (supports_session_resume)
|-- register AgentExecution(session_id, runtime=local_docker)
|-- executors_running.status = ready (workspace-only enabled)
|
|-- if supports_session_resume & resume_token present:
| ResumeSession
| session.state = WAITING_FOR_INPUT
| else:
| session.state = ERROR (not_resumable)
|
'-- WS events to UI:
session.agentctl_ready / session.state_changed
Local Standalone Executor (agentctl does NOT survive)
Backend Restart
|
|-- load executors_running (starting/ready)
|-- no local instances to recover
|-- LaunchAgent from executors_running (workspace-only)
|-- executors_running.status = starting
|-- agentctl Health()
|-- agentctl Capabilities? (supports_session_resume)
|-- executors_running.status = ready (workspace-only enabled)
|
|-- if supports_session_resume & resume_token present:
| ResumeSession
| session.state = WAITING_FOR_INPUT
| else:
| session.state = ERROR (not_resumable)
|
'-- WS events to UI:
session.agentctl_starting / ready / error
Remote Executor (agentctl survives remotely)
Backend Restart
|
|-- load executors_running: agentctl_url, id
|-- connect to agentctl Health()
|-- agentctl Capabilities? (supports_session_resume)
|-- register AgentExecution(session_id, runtime=remote)
|-- executors_running.status = ready (workspace-only enabled)
|
|-- if supports_session_resume & resume_token present:
| ResumeSession
| session.state = WAITING_FOR_INPUT
| else:
| session.state = ERROR (not_resumable)
|
'-- WS events to UI:
session.agentctl_ready / session.state_changed
Executor vs Agent Resumption
Separating executor resume from agent resume allows:
- Shell/git/files to function as soon as agentctl is reachable.
- Agent resume to fail gracefully without losing workspace access.
Example UI behavior:
session.agentctl_ready-> enable shell/git/files panels.- If
supports_session_resume=false-> show “Create new session to continue messaging.” - If resume fails -> show error but keep workspace panels active.