DocumentsDosu Decant
Session Lifecycle Management
Session Lifecycle Management
Type
Topic
Status
Published
Created
Aug 4, 2026
Updated
Aug 4, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Session Lifecycle Management#

Sessions in Decant have three visibility states — visible (default), archived, and deleted — stored as local metadata that never touches the underlying JSONL source files .

State is persisted in the session_user_state table, keyed by (tool, source_session_id). This composite key outlives the physical session row, which is what makes tombstone-based deletion durable.


Archiving#

Archiving is a soft-hide: the session row stays in the database but is excluded from default list, search, and statistics results .

  • Opt-in: Pass include_archived=true to GET /api/sessions or statistics endpoints to re-include archived sessions .
  • Search is always filtered: GET /api/sessions/search-index (the command-palette haystack) never surfaces archived sessions, regardless of any parameter . Full-text search is similarly limited to visible sessions.
  • Ancestor propagation: Archive state is inherited from ancestors via a recursive CTE — if a parent session is archived, child/sub-agent sessions are also hidden. See sessionUserStatePredicate().

The state mutation endpoint is POST /api/sessions/{id}/state with body { "state": "archived" | "visible" | "deleted" }.


Deletion (Tombstones)#

Deletion is a hard delete with tombstone protection: the session subtree is physically removed from the database, and tombstone rows are written to session_user_state with state = 'deleted' so that a subsequent sync does not restore the session .

The setSessionUserState() function handles two categories of tombstones on deletion:

  1. Direct tombstone — a (tool, source_session_id) row for the deleted session itself.
  2. Spawn tombstones (Claude Code only) — child transcripts spawned via tool calls are tracked under the reserved namespace __decant_internal_claude_spawn__, keyed as JSON.stringify([rootSourceSessionId, spawnToolUseId]) . When a child transcript is later encountered during sync, inheritDeletedSessionTombstone() propagates the deletion.

Ingest source entries that no longer have any live sessions pointing to them are marked status = 'skipped_deleted' . The source JSONL file is never modified.


Effect on List and Statistics Operations#

OperationDefault (include_archived=false)include_archived=true
GET /api/sessionsExcludes archived + deletedExcludes deleted only
Statistics endpointsExcludes archived + deletedExcludes deleted only
GET /api/sessions/search-indexExcludes archived + deleted(flag not supported)

The shared predicate sessionUserStatePredicateForDatabase() is applied consistently across listSessions() in src/query.ts and statsScope() in src/stats.ts. When no hidden state exists in the database, the predicate optimizes to a no-op.

Deleted sessions are always excluded — there is no flag to surface them after deletion.


Key Source Locations#

ConcernFile / Lines
State mutation routesrc/server.ts lines 379–423
setSessionUserState()src/session-user-state.ts lines 207–263
Visibility predicatesrc/session-user-state.ts lines 50–69
session_user_state schemasrc/schema lines 177–182
List query filteringsrc/query.ts lines 138–181
Statistics filteringsrc/stats.ts lines 105–136
API route semanticsroutes