States

A session is always in one lifecycle state:

StateMeaning
runningAgent process is alive
stoppedAgent process was stopped; worktree and branch are preserved
erroredAgent process exited with a non-zero exit code
creatingSession is being set up (transient)
deletingSession is being torn down (transient)

Running sessions also carry an agent status for current activity (active, ready, or error, plus tool names from hook reports) — separate from lifecycle state and not persisted. An agent waiting in its own permission TUI stays running; Graith doesn’t create or answer an approval status.

Creation

  gr new fix-auth-bug
  

Steps:

  1. Fetchgit fetch origin when fetch_on_create = true; --no-fetch skips (offline or no SSH auth)
  2. Branch<branch_prefix>/<session-name>-<session-id> from the base branch (default: repo default, override --base)
  3. Worktree – git worktree at <data_dir>/worktrees/<repo-name>/<repo-hash>/<session-id>/
  4. Environment – sets GRAITH_SESSION_ID, GRAITH_SESSION_NAME, GRAITH_AGENT_TYPE, GRAITH_WORKTREE_PATH, GRAITH_REPO_PATH, GRAITH_TMPDIR, TMPDIR
  5. Sandbox – when enabled, requires a configured backend and wraps the command (safehouse wrap or nono run --profile); explicitly disabled starts with a warning
  6. Agent – starts with optional non_interactive_args then args; clear the prefix to keep the native approval TUI
  7. Prompt (--prompt/--prompt-file) – typed into stdin after startup
  8. Attach (unless --background) – enters passthrough mode

For a freshly initialized repository with no commits, Graith uses the unborn HEAD branch as the base and creates the generated session branch in an empty, isolated orphan worktree. The first commit therefore belongs to the session branch; Graith does not create a bootstrap commit or change the source checkout. If --base is supplied, it must name the unborn HEAD branch. This workflow requires Git 2.42 or newer.

Variants

No repo: gr new scratch --no-repo — a scratch directory, no git repo or worktree.

In-place: gr new quick --in-place runs the agent directly in the repo, no worktree or branch. --allow-concurrent permits multiple in-place sessions on one repo.

Mirror: gr new observer --mirror my-session mounts another session’s worktree read-only, for observation or review. Add --headless -p "…" to opt into a one-shot stream-json session. Requires Graith’s enforceable sandbox for the read-only guarantee.

Read-only branch: gr new observer --repo ~/Code/app --read-only --base main creates a detached worktree at the selected branch revision, launches the agent from a writable scratch directory, and mounts the repository state read-only. If --base is omitted, Graith uses the repository default branch. Creation follows the normal fetch policy (fetch_on_create, with --no-fetch as a creation-time override); resume/restart refreshes the detached worktree to the latest resolved branch revision. Requires Graith’s enforceable sandbox. Read-only branch sessions do not support includes, and can’t be combined with --mirror, --in-place, or --no-repo.

Mirrors and read-only branch sessions are both classified as read-only/mirror sessions, so file-watch triggers skip them before creating bindings. Use --mirror when the session should inspect another live session’s exact worktree, including uncommitted changes. Use --read-only when the session only needs a current repository branch reference. Use a normal worktree or --in-place for agents that need to edit or commit.

Headless sessions

Experimental. gr new watcher --headless -p "…" runs the agent in Claude Code’s stream-json mode instead of a PTY — non-interactive, for fire-and-forget work like review judges, one-shot helpers, and trigger actions. graith parses the typed event stream, so gr logs -f renders it and cost/token usage comes from the result envelope. v1 is Claude-only, one-shot (one prompt, run to completion, exit), requires a prompt, uses the same optional Graith sandbox setting as PTY sessions, and implies --background. It’s inert unless [headless] experimental = true, and can’t be resumed as headless once it exits. See Configuration → Headless sessions.

With no PTY to stream, gr attach converts a headless session to interactive: it stops the headless process and relaunches via claude --resume <session-id>, preserving conversation, worktree, branch, and env. This restarts the agent, so attach confirms first (in-flight tool calls are cancelled, not resumed); -y/--yes skips. To watch read-only without converting, use gr logs -f.

Interrupts and permission errors. Over Claude Code’s stdin control protocol, graith cleanly interrupts an in-flight turn instead of firing terminal signals. Native tool-permission requests are denied immediately and mark the driver degraded — no native TUI or user-response channel. Graith does not add a semantic shell-command policy to headless or PTY sessions; configure an agent-native hook or external policy tool directly when one is required.

Attachment

  gr attach fix-auth-bug
  

Attaching connects your terminal’s stdin/stdout to the session’s PTY through the daemon. Only one client attaches at a time; a new client kicks the previous.

For PTY sessions, the daemon-side terminal model owns terminal query replies. Attached clients render visible output but filter query sequences and unsafe OSC/DCS/APC side-effect protocols before they reach the host terminal, so a detached session and an attached session expose the same child-facing terminal authority.

The attach loop transitions between passthrough mode (raw terminal I/O) and the Session Navigator, cycling without dropping the daemon connection: detach with ctrl+b d, switch sessions with ctrl+b n/p/l.

Live attach output is bounded per client so a slow terminal or remote link cannot stall the session’s PTY drain. Raw attach and gr logs -f preserve byte order, coalesce adjacent queued chunks, and buffer until the per-client queue reaches 1 MiB or 16,384 chunks. Graith disconnects a client whose queue overflows or whose queued write cannot complete within 2 seconds, and the session keeps running. Terminal-owned attach coalesces live output into repaint hints because snapshots carry the screen state.

Interactive attach renders from daemon-maintained screen snapshots by default instead of forwarding all child terminal control traffic directly to your emulator. The daemon sends a coherent current-screen seed before live output resumes, including bounded terminal-aware primary-screen history retained by the daemon, and the client owns the outer alternate screen for that attach. Existing sessions created with the removed gr new --experimental-attach flag need no manual migration; upgraded clients request terminal-owned attach for every interactive session, and the daemon drops the obsolete persisted state key the next time it saves state. The control protocol is bumped to 3.0 so older clients and daemons do not silently reconnect through the removed experimental attach request.

CLI attach requires that seed; if the daemon cannot provide one, attach fails with an error. The initial terminal-owned attach uses a full-screen seed; later refreshes use dirty-row updates when the client and daemon share a compatible snapshot base, falling back to full snapshots when needed. The daemon-side terminal model is the only authority for terminal query replies, so attached clients filter those query sequences and unsafe OSC/DCS/APC side-effect protocols before they reach the host terminal. Window-title changes stay model-local, clipboard writes are denied, hyperlink labels remain visible but are not forwarded as host hyperlinks, and notification/image protocols are ignored or stripped. Graith mirrors the child’s mouse, focus, bracketed-paste, application cursor-key, and keypad modes from the same daemon-side terminal model. Wheel events go to child mouse tracking when requested, use alternate-scroll cursor keys for alternate-screen children without mouse tracking, or trigger a configured Graith input gesture such as mouse_wheel_up = "scroll_mode" when the effective [input] policy allows it. Host-terminal scrollback is not the history surface in this mode; ctrl+b [ can inspect the retained formatted attach seed until live output advances the screen, then falls back to raw logs. Pixel-coordinate mouse reporting is still incomplete. The Graith status bar is composed inside the owned frame, with read-only attach still replacing it with the persistent read-only indicator. When the status bar or read-only indicator is visible and the terminal has at least two rows, Graith reserves one top or bottom row according to [status_bar].position and resizes the child PTY to the remaining rows. The reserved row is not part of the child screen snapshot or host-terminal scrollback; cursor and mouse cell coordinates that reach the child are translated into the child viewport. If the terminal is only one row tall, Graith suppresses the chrome row so the child keeps a usable line.

Detachment

Press ctrl+b d to detach. The agent keeps running; the daemon maintains the PTY and buffers output to a scrollback file.

Stop and resume

  gr stop fix-auth-bug       # kill agent process, keep worktree
gr restart fix-auth-bug    # restart with resume_args in existing worktree
  

Stopping sends SIGTERM (worktree and branch preserved); restarting resumes the previous conversation via resume_args.

Agents with resume_args default to a 1-hour idle timeout, after which the daemon stops the session automatically — resume it later.

Fork

  gr fork fix-auth-bug auth-approach-2
  

Forking explores alternative approaches from the same git state:

  1. Creates a new worktree from the source session’s current branch
  2. Creates a new branch
  3. Starts a new agent using fork_args (if configured) — typically passing the source’s session ID so it inherits conversation history. Without fork_args, only git/worktree state is forked and the agent starts with its regular args
  4. The source session is unaffected

Cross-agent fork

  gr fork fix-auth-bug auth-codex --agent codex
  

Pass --agent <target> to fork into a different agent. Since the target can’t natively resume the source’s conversation, graith renders the source’s history to a neutral context file (the renderer gr migrate uses) and seeds the new agent with it. The original keeps running, so both work in parallel.

Unlike gr migrate (worktree in place), a fork branches a new worktree from the base branch — so the source’s changes (uncommitted edits and any commits on its branch) don’t carry over; re-apply any you need. Claude and Codex are supported as fork sources; any configured agent can be a target. Use --model to override the target’s model.

Migrate to a different agent

  gr migrate fix-auth-bug --agent codex
  

Migration swaps the agent on a session in place — most useful during a provider outage (e.g. Claude API down, keep working in Codex). Unlike fork, it creates no new worktree or branch; the session keeps its id, name, worktree, and branch.

Migrating:

  1. Renders the current conversation to a neutral Markdown context file (fail-fast: missing or empty transcript changes nothing)
  2. Stops the current agent
  3. Switches the session’s agent type (and model, via --model)
  4. Starts the target agent in the same worktree, seeded with the rendered context
  5. Runs a health check; if the target fails to start, the original agent is restored

The retained worktree carries over all code state (commits and uncommitted edits), no branching. The handoff is a lossy reseed, not a native resume: hidden reasoning/thinking and exact tool-call replay aren’t transferred, and the process restarts (attached clients re-attach).

Claude and Codex are supported as migration sources (transcript formats graith can read); any configured agent can be a target, including system sessions like the orchestrator. Migration is soft-reversible — gr list records the agent migrated from, so gr migrate ... --agent <original> hands the work back.

Deletion

  gr delete fix-auth-bug
  

gr delete is a recoverable soft delete: it stops the agent if needed, hides the session from normal lists, and preserves its state and artifacts for the configured retention window. Recover with gr restore.

gr purge or retention cleanup performs destructive teardown:

  1. Kills the agent process (if running)
  2. Removes owned worktrees and scratch directories
  3. Deletes generated branches when the session owns one
  4. Removes the session from state

Normal worktree sessions own a generated branch and worktree. In-place sessions do not own the repository checkout. Session mirrors do not remove the source worktree they observe. Branch-backed read-only sessions own a detached worktree and writable scratch directory, but no generated branch.

Parent-child relationships

When a session spawns children (e.g. an orchestrator and its workers), graith tracks the link via parent_id.

  gr list                           # show hierarchy (the default)
gr list --flat                    # use flat repo/name ordering
gr list --children my-session     # show descendants
gr stop --children                # stop all children (from inside a session)
gr delete --children              # delete all children
gr msg send --children "rebase"   # message all descendants
gr msg send --parent "done"       # message the parent
  

Without a positional argument inside a graith session, --children auto-detects the current session from GRAITH_SESSION_ID and excludes it.

Environment variables

The daemon sets these in every agent process:

VariableValue
GRAITH_SESSION_IDUnique session ID
GRAITH_SESSION_NAMEHuman-readable session name
GRAITH_AGENT_TYPEAgent type (e.g. claude, codex)
GRAITH_WORKTREE_PATHAbsolute path to the worktree. For mirror and read-only branch sessions, this is the read-only repository path; the process cwd is writable scratch.
GRAITH_REPO_PATHAbsolute path to the source repository (canonical)
GRAITH_TMPDIRPer-repo temporary directory (persists across sessions)
TMPDIRSet to GRAITH_TMPDIR

When includes are configured:

VariableValue
GRAITH_INCLUDE_<BASENAME>_PATHAbsolute path to each included repo’s worktree

State persistence

Session state lives in state.json in the data directory, loaded on daemon start and saved on every mutation, so sessions survive restarts. This includes the authoritative cwd assigned at launch, which can differ from the Git worktree for mirror, read-only branch, and system sessions and is reused on resume. Runtime-only state (hook reports, attached clients) isn’t persisted — it’s rebuilt on restart. Status and protocol JSON expose read_only_branch and read_only_revision for branch-backed read-only sessions, alongside the source repo_path, selected branch, and base_branch.

Labels

Labels are durable organizational metadata for relating sessions across repositories. Add them at creation or update them individually:

  gr new api-fix --label urgent --label customer:Brae
gr update api-fix --add-label release --remove-label urgent
gr list --label release
gr list --label release --label customer:brae  # AND matching
  

Leading and trailing Unicode whitespace is trimmed. A label must be non-empty, at most 64 UTF-8 bytes, contain no control characters or commas, and each session may have at most 32 distinct labels. Commas delimit labels in the TUI and native forms. Identity uses locale-independent Unicode simple case folding (Urgent and urgent are the same label), while the first stored display spelling is retained. No Unicode canonical normalization is applied, so canonically equivalent but differently encoded text remains distinct. Duplicate input is deduplicated in order.

Labels persist through daemon restart, in-place agent migration, and soft delete/restore. Existing state is migrated to an empty set. Child sessions inherit a snapshot of the parent’s labels by default, including scenario, trigger, and mirror-created children. Supplying labels during creation replaces the inherited set instead of merging with it. Forks inherit a snapshot of the source labels. Later label changes on parent, child, source, or fork are independent. Labels never grant access: creation and update use the same local-user, remote-user, session/descendant, and system-session boundaries as the rest of session metadata.

The Session Navigator has a Labels view, grouping matching sessions across repository boundaries and displaying each match as repo/session. Each label group preserves parent/child relationships, including those crossing repositories. Search also matches label text; if filtering removes a parent, its matching child becomes a tree root. The macOS and iOS sidebars use the same shared label filter and expose label fields when creating or editing a session.

Scrollback

Each session’s PTY output is appended to <data_dir>/logs/<session-id>.log, for tail reads by gr logs and preview rendering in the Session Navigator.

gr doctor warns on oversized scrollback files; gr doctor --autofix truncates them.

Starring

  gr update important-session --starred
gr update important-session --starred=false
  

Starred sessions:

  • Can’t be deleted — set --starred=false first
  • Are skipped by batch stop/delete operations (e.g. --stale, --stopped)
  • Can still be stopped directly (gr stop <session>)
  • Appear in the Starred view in the Session Navigator
  • Show a star indicator in the session list

--starred combines with --name and --parent; the daemon validates and persists them as one update. Repeating the same true/false value is safe.

Status summaries

Agents or users can set a status summary shown in the Session Navigator:

  gr status "Exploring code"
gr status --ttl 30m "Waiting for CI"
gr status --clear
  

The status auto-expires when the agent produces output without updating it (default TTL: 5 minutes); while idle it fades but persists visually.

With no explicit status, the Session Navigator auto-derives activity summaries from hook reports (e.g. “Using Bash”, “Using Edit”).