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=truetoGET /api/sessionsor 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:
- Direct tombstone — a
(tool, source_session_id)row for the deleted session itself. - Spawn tombstones (Claude Code only) — child transcripts spawned via tool calls are tracked under the reserved namespace
__decant_internal_claude_spawn__, keyed asJSON.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#
| Operation | Default (include_archived=false) | include_archived=true |
|---|---|---|
GET /api/sessions | Excludes archived + deleted | Excludes deleted only |
| Statistics endpoints | Excludes archived + deleted | Excludes deleted only |
GET /api/sessions/search-index | Excludes 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#
| Concern | File / Lines |
|---|---|
| State mutation route | src/server.ts lines 379–423 |
setSessionUserState() | src/session-user-state.ts lines 207–263 |
| Visibility predicate | src/session-user-state.ts lines 50–69 |
session_user_state schema | src/schema lines 177–182 |
| List query filtering | src/query.ts lines 138–181 |
| Statistics filtering | src/stats.ts lines 105–136 |
| API route semantics | routes |