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

244 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.