Both engines persist sessions, but to different stores with different lifecycle semantics. This page documents each separately.

Two persistence stores

EngineStoreLoader
Claude SDK<CLAUDE_CONFIG_DIR>/projects/<conversationId>/SDK-internal
Pi<ORION_APP_DATA_DIR>/pi-sessions/<conversationId>.jsonSessionManager.continueRecent(conversationId)
CLAUDE_CONFIG_DIR is overridden to prompts/ for runtime isolation. See .claude/rules/sdk/orion-isolation-architecture.md.

Lifecycle

Mechanics

  1. Spawnquery() internally spawns cli.js as a subprocess with the configured canUseTool closure.
  2. Init — The first event from the SDK is a system init message carrying the session_id. Captured at message-processor.mjs:733. ✓ VERIFIED
  3. Streamstream_eventtext SidecarEvents; assistant messages carry thinking + tool_use blocks; user messages with tool_result close out tool calls.
  4. Persist — On result, the SDK writes to projects/<conversationId>/ automatically.
  5. Resume — Pass sdkSessionId on the next query to resume in-place.
Never set persistSession: false — it breaks resume. Sessions persist to prompts/projects/. From CLAUDE.md Known Gotchas #7.

conversationId vs sessionId

Orion uses both terms for adjacent concepts. They are not interchangeable.
TermWhat it isUsed by
sessionIdOrion’s session_index.idUI sidebar, fork/merge lineage, cross-table joins
conversationIdClaude SDK’s session UUID (also Pi’s persistence key); stored as sdkSessionIdPersistence (both engines), SDK resume, Pi AgentSession cache
See Reference → Glossary for the full disambiguation. The handoff’s pi_session_model finding notes “Pi maps conversationId to disk-backed SessionManager files” — that’s conversationId, not sessionId. ✓ VERIFIED

Session table invariant

Both session_index and conversations tables must be updated atomically when session type or project linking changes. From .claude/rules/data/orion-session-tables.md:
// ❌ WRONG - Only updates conversations
UPDATE conversations SET type = 'project' WHERE id = ?

// ✓ CORRECT - Updates both tables
UPDATE conversations SET type = 'project' WHERE id = ?
UPDATE session_index SET type = 'project' WHERE conversation_id = ?
Forks and merges follow the same dual-table invariant inside a single rusqlite transaction. See session_lineage.rs.

Next

Event Layer

Both engines emit native events. How does the frontend get a unified stream?

Subagents

Both engines support nested subagents — with asymmetric codepaths.