Files
kandev/docs/session-resumption-ll.md
T
2026-01-17 19:02:22 +00:00

9.8 KiB
Raw Blame History

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_token in 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_sessions
    • id, task_id, state, agent_profile_id, agent_execution_id, metadata (includes resume_token)
    • worktree_id, worktree_path, worktree_branch
    • executor_id (local/remote)
  • executors_running (persistent table)
    • id (executor instance id), session_id, task_id, runtime
    • agentctl_url, agentctl_port, container_id (docker), pid (local)
    • status: starting | ready | error | stopped
    • supports_session_resume, supports_workspace_only
    • resume_token
    • workspace_path, worktree_id, worktree_path, worktree_branch
    • last_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_resume boolean 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.

  1. Load executors

    • Query executors_running where status in (starting, ready).
  2. Reject non-resumable executors

    • If supports_session_resume=false, mark related sessions ERROR and set error_message=not_resumable.
  3. Resume executors by runtime

    • Local Docker:
      • Verify Docker daemon running.
      • Locate containers by executors_running.container_id or session label.
      • If container missing: mark executor error, mark sessions ERROR.
      • If container running: healthcheck agentctl.
    • Local Standalone:
      • Verify worktree path exists.
      • If missing: mark executor error, mark sessions ERROR.
      • Start agentctl process and healthcheck.
    • Remote Executor:
      • Call Health() on agentctl_url.
      • If unreachable: mark executor error, mark sessions ERROR.
  4. Sync executor cache

    • Register running executions in executors_cache keyed by session_id.
    • Update executors_running.last_seen_at.
  5. Enable workspace access

    • If agentctl is healthy, set executors_running.status=ready.
    • Shell/fs/git are enabled regardless of agent resume.
  6. Resume agent session

    • If supports_session_resume=true and resume_token present, resume agent.
    • On success: set session.state = WAITING_FOR_INPUT.
    • On failure: set session.state = ERROR, update error_message.
  7. 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.

  1. Validate session:

    • Session exists and belongs to task.
    • Agent profile supports resume and session has resume_token.
    • Worktree exists if required.
  2. Create agentctl instance (local only):

    • Call LaunchAgent with session_id, worktree_path, worktree_branch.
    • Persist executors_running.status=starting.
  3. Start agent process and resume:

    • Resume agent session with resume_token.
    • On success:
      • session.state = WAITING_FOR_INPUT
      • executors_running.status = ready
      • task.state = REVIEW
    • On failure:
      • session.state = ERROR
      • executors_running.status = error
      • Persist error_message.

Session Status API

task.session.status

Return session-specific status (no task-level fallback):

  • session_id, task_id, state, agent_profile_id
  • executor_status, agent_execution_id, runtime
  • is_agent_running (based on execution for that session)
  • is_resumable (resume supported + resume_token present)
  • 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_starting
  • session.agentctl_ready
  • session.agentctl_error
  • session.state_changed
  • session.message.added, session.message.updated

Payloads must include session_id and task_id.

Frontend Flow

  1. SSR page loads session + task + messages and hydrates store.
  2. 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.
  3. 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.status must only check execution.SessionID == sessionID.
  • WebSocket subscriptions should be per-session to avoid cross-session updates.

Non-Resumable Agents

  • Add supports_session_resume=false in agent registry/profile.
  • Backend returns is_resumable=false in status.
  • UI displays a banner: “This agent doesn’t support resuming. Start a new session.”

Open Decisions

  • Whether to keep executors_running lean 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:

  1. session.agentctl_ready -> enable shell/git/files panels.
  2. If supports_session_resume=false -> show “Create new session to continue messaging.”
  3. If resume fails -> show error but keep workspace panels active.