Skip to content

Sessions

A session is the fundamental unit of work in Magia. It represents a single, isolated conversation with an AI agent that operates inside a specific project directory.

When you create a session you choose a working directory. The agent process (e.g. the Claude Code CLI) is spawned with that directory as its root. Every file read, edit, or shell command the agent executes is scoped to that directory tree. Two sessions pointing at the same directory are fully independent — they share no in-memory state and no conversation history.

Each session carries:

FieldDescription
idA UUID that is the single source of truth for the session (see Two ID spaces below)
projectPathThe absolute filesystem path the agent operates in
providerWhich LLM CLI backs this session (claude, gemini, codex)
statusCurrent lifecycle state (see Session lifecycle)
title + titleSourceDisplay name and how it was set (see Session naming)

Magia maintains two separate identifier namespaces for every session:

  • Workspace UUID (session.id) — A UUID generated by Magia. This is the stable, permanent identifier used throughout the UI, the sidebar, and all Tauri commands. It does not change across resumes or forks.
  • CLI session ID (session.claudeSessionId, deprecated field) — The session ID assigned by the underlying CLI tool. For Claude Code this is the value passed via --session-id and stored in the .claude/projects/ JSONL files. For Gemini and Codex, the CLI generates its own internal IDs.

The two spaces are bridged: when an agent process emits events, those events carry the CLI-level session ID; Magia maps them back to the workspace UUID via an in-memory index so the UI always shows events against the correct session record.

Practical implication: When resuming a session Magia passes the workspace UUID as the --session-id to Claude Code, keeping the two IDs in sync for Claude sessions. For turn-based providers (Gemini, Codex) Magia uses its own UUID for routing only.

create → running → idle → closed
↘ error → dead
StatusMeaning
runningThe agent process is actively executing a task
idleThe agent has finished its last turn and is waiting for the next message
errorThe agent process exited with a non-zero status or the startup watchdog fired
closedThe session was explicitly stopped
deadThe process disappeared unexpectedly (e.g. OOM kill)

Sessions are created by calling create_agent_session on the Rust backend. For persistent providers (Claude Code), one long-running process is spawned and messages are written to its stdin. For turn-based providers (Gemini CLI, Codex CLI), the process creation is deferred until the first message is sent; each subsequent message spawns a new process with --resume flags.

On app shutdown, all managed agent processes are killed to prevent orphaned processes from consuming API credits.

The display name of a session is derived from two fields:

FieldTypeDescription
titlestring | nullThe current title value
titleSource"user" | "auto" | "prompt" | nullWhich mechanism set the title

A strict priority hierarchy governs which source can overwrite an existing title:

null < prompt < auto < user
  • null — No title has been set; the UI falls back to "New Session".
  • prompt — Magia extracted the first user prompt from the session transcript during discovery. This is the lowest automatic source.
  • auto — An AI-generated title suggestion was accepted.
  • user — The user explicitly renamed the session. This is never overwritten automatically.

The helper resolveSessionTitle(session) returns session.title ?? "New Session" and is the canonical way to get a displayable name.

Sessions track their lineage through two optional fields:

  • resumedFrom — The ID of the session this one was resumed from. A resumed session picks up the conversation history of the original and continues from where it left off.
  • forkedFrom — The ID of the session this one was forked from. A forked session starts a new, independent conversation from a specific branch point in the parent session’s history, optionally restoring the git working tree state at that moment.

Branch points are lightweight records that store the conversation event index and an optional git snapshot ref (refs/magia/snapshots/{uuid}). Forking does not create a git worktree immediately — that happens on demand when the user chooses to fork from a branch point.

Sessions can be pinned to the top of the sidebar for quick access. Pinned sessions are ordered manually (drag-to-reorder). A configurable maximum (maxPinnedSessions, 0 = unlimited) prevents the pinned section from growing indefinitely — when the limit is reached, the oldest pinned session is automatically unpinned.

Pinning is stored per-session via pinnedAt (Unix timestamp, null = not pinned) and pinnedOrder (sort position within the pinned section).

Sessions can be marked hidden: true to remove them from the default sidebar view without deleting them. Hidden sessions are excluded from discovery and list views unless the user explicitly enables “show hidden sessions”.

The sidebar supports filtering sessions by:

  • Free-text search across title, project path, and first prompt
  • Provider (claude, gemini, codex)
  • Status (running, idle, error, closed)

Beyond the primary projectPath, a session can have additionalDirs — extra directories added during a session (e.g. via the /add-dir command in chat). These are passed to the CLI as additional context paths and are persisted with the session record.

Session metadata is persisted to sessions.json in Magia’s app data directory (~/Library/Application Support/sh.magia.com/ on macOS, sh.magia.dev/ in dev mode). The Zustand store is an in-memory cache; the Rust backend is the source of truth for session files.