Status: Active Owner: Maintainers Source of truth: this document for its stated scope Parent: Feature Documentation
CueLoop's session management system provides crash recovery, explicit resume decisions, and runner-level continue support for long-running agent work.
Session management serves two related purposes:
- Run-session recovery: detect interrupted
run one/run loopwork and decide whether to resume, start fresh, or refuse to guess. - Continue-session recovery: keep runner sessions alive across CI-fix / revert-and-continue loops when the underlying runner supports reuse.
CueLoop now narrates resume behavior with one of three states:
| State | Meaning |
|---|---|
resuming_same_session |
CueLoop is continuing the interrupted run or runner session. |
falling_back_to_fresh_invocation |
CueLoop decided the saved state should not be reused and is starting fresh. |
refusing_to_resume |
CueLoop cannot safely choose resume vs fresh without operator confirmation. |
Those decisions appear across:
cueloop run onecueloop run loopcueloop run resumecueloop machine config resolvecueloop machine run ...event streams- CueLoopMac Run Control
| Feature | Description |
|---|---|
| Explicit recovery narration | CueLoop says whether it is resuming, starting fresh, or refusing. |
| Configurable timeout | Sessions older than session_timeout_hours require explicit confirmation. |
| Read-only previews | Machine/app config preview can show resume state without mutating cache. |
| Per-phase runner isolation | Continue sessions stay phase-scoped, including deterministic Kimi session IDs. |
| Atomic persistence | Session state is written atomically to prevent corruption. |
Session state is persisted to:
.cueloop/cache/session.jsonc
This file is created when a task starts and is normally cleared when the run completes successfully or when CueLoop explicitly abandons an invalid saved session during execution.
{
"version": 1,
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"task_id": "CL-0001",
"run_started_at": "2026-02-07T10:00:00.000000000Z",
"last_updated_at": "2026-02-07T10:30:00.000000000Z",
"iterations_planned": 2,
"iterations_completed": 1,
"current_phase": 2,
"runner": "claude",
"model": "sonnet",
"tasks_completed_in_loop": 0,
"max_tasks": 10,
"git_head_commit": "abc123def456",
"phase1_settings": {
"runner": "claude",
"model": "sonnet",
"reasoning_effort": null
},
"phase2_settings": {
"runner": "codex",
"model": "gpt-5.4",
"reasoning_effort": "high"
},
"phase3_settings": {
"runner": "claude",
"model": "haiku",
"reasoning_effort": null
}
}task_id,current_phase, andtasks_completed_in_loopdrive crash-recovery routing.phase*_settingsare display-only; CueLoop recomputes effective settings from config + task + CLI overrides.git_head_commitis advisory context, not a hard resume gate.
When CueLoop starts a run, it classifies saved session state into one of these buckets:
| Validation result | Meaning |
|---|---|
NoSession |
No interrupted run exists. |
Valid(session) |
Session still targets a live runnable task. |
Stale |
Task disappeared or entered a terminal / incompatible state. |
Timeout |
Session is older than the configured safety threshold. |
- Behaves like
run loop --resume. - Valid sessions resume immediately.
- Timed-out sessions still require confirmation.
- If no saved session exists, CueLoop explicitly says it is starting fresh.
- Always inspects interrupted-session state first.
--resumeauto-resumes when safe.- Without
--resume, CueLoop prompts when confirmation is required and available. - If you explicitly pass
--id <TASK_ID>, that selection overrides an unrelated interrupted session and CueLoop says so.
- Supports the same session decision model as
run one. - A resumed task is used only for the first loop iteration, then normal queue selection resumes.
Sessions older than session_timeout_hours are not auto-resumed just because --resume is present.
Timed-out sessions require an explicit operator confirmation unless CueLoop is in a non-interactive context, in which case it refuses instead of guessing.
If a saved session requires a decision and CueLoop cannot ask safely:
| Situation | Result |
|---|---|
Valid session + no --resume |
refusing_to_resume |
| Timed-out session | refusing_to_resume |
| No saved session | start fresh |
| Stale session | start fresh |
This prevents headless automation from silently discarding or duplicating interrupted work.
cueloop machine config resolve now includes an optional resume_preview payload:
{
"version": 5,
"paths": {
"repo_root": "/repo",
"queue_path": "/repo/.cueloop/queue.jsonc",
"done_path": "/repo/.cueloop/done.jsonc"
},
"safety": {
"repo_trusted": true,
"dirty_repo": false,
"git_publish_mode": "off",
"ci_gate_enabled": false,
"git_revert_mode": "ask",
"parallel_configured": false,
"execution_interactivity": "noninteractive_streaming",
"interactive_approval_supported": false
},
"execution_controls": {
"runners": [],
"reasoning_efforts": [
"low",
"medium",
"high",
"xhigh"
],
"parallel_workers": {
"min": 2,
"max": 255,
"default_missing_value": 2
}
},
"config": { "agent": { "model": "gpt-5.4" } },
"resume_preview": {
"status": "refusing_to_resume",
"scope": "run_session",
"reason": "session_timed_out_requires_confirmation",
"task_id": "CL-0001",
"message": "Resume: refusing to continue timed-out session CL-0001 without explicit confirmation.",
"detail": "The saved session is 48 hour(s) old, exceeding the configured 24-hour safety threshold."
}
}This preview is read-only: it must not clear or rewrite saved session state.
cueloop machine run ... streams can emit:
{
"version": 3,
"kind": "resume_decision",
"timestamp": "2026-04-26T06:00:00Z",
"task_id": "CL-0001",
"message": "Resume: continuing the interrupted session for task CL-0001.",
"payload": {
"status": "resuming_same_session",
"scope": "run_session",
"reason": "session_valid",
"task_id": "CL-0001",
"message": "Resume: continuing the interrupted session for task CL-0001.",
"detail": "Saved session is current and will resume from phase 2 with 1 completed loop task(s)."
}
}CueLoopMac consumes both resume_preview and resume_decision so Run Control can show the expected action before the run starts and the actual action once the run begins.
Run-session recovery decides whether CueLoop resumes a task. Continue-session recovery decides whether CueLoop can reuse the runner's own session during CI-fix / supervision loops.
- CueLoop prefers same-session reuse when a runner session identifier exists.
- If the session identifier is missing or known-invalid, CueLoop says it is falling back to a fresh invocation.
- Unknown resume failures still hard-fail.
| Runner | Safe fresh fallback cases |
|---|---|
| Pi | missing session file / lookup failures |
| Gemini | invalid session identifier resume failures |
| Claude | invalid --resume / invalid UUID failures |
| OpenCode | session validation failures, including semantic zero-exit failures |
For runners that support explicit session IDs (notably Kimi), CueLoop uses deterministic per-phase identifiers:
{task_id}-p{phase}-{timestamp}
Example:
CL-0001-p2-1704153600
This keeps planning / implementation / review recovery isolated from each other.
Configure timeout behavior in .cueloop/config.jsonc:
{
"agent": {
"session_timeout_hours": 24
}
}Guidance:
- daily development:
24 - long weekend work:
72 - extended analysis:
168 - CI/headless automation: keep low and pair with an explicit
--resumepolicy
- Review the resume message before continuing old work.
- Use
cueloop run one --resumeorcueloop run resumewhen you want an explicit auto-continue path. - Treat
refusing_to_resumeas a prompt to choose deliberately, not as an error to suppress blindly.
- Read
resume_previewfor preflight UI. - Consume
resume_decisionrun events for live state. - Do not infer resume behavior from plain text when machine payloads exist.
- Prefer an explicit policy:
# Explicitly continue when safe
cueloop run loop --resume --non-interactive
# Or require fresh orchestration with no recovery
cueloop run loop --non-interactiveCommon causes:
- the task no longer exists
- the task is already terminal (
done,rejected) - you explicitly selected a different task
- the saved session was stale
Common causes:
- non-interactive mode prevented a required confirmation
- the saved session timed out and needed operator approval
Common causes:
- the runner rejected the saved session id
- no runner session id was available to reuse