{"id":50631678,"url":"https://github.com/d0ugal/graith","last_synced_at":"2026-07-02T12:00:56.563Z","repository":{"id":362948046,"uuid":"1261358395","full_name":"d0ugal/graith","owner":"d0ugal","description":"A terminal multiplexer for AI coding agent sessions, each in an isolated git worktree. Go.","archived":false,"fork":false,"pushed_at":"2026-06-30T05:39:34.000Z","size":10224,"stargazers_count":2,"open_issues_count":78,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T07:17:34.125Z","etag":null,"topics":["ai-agents","cli","git-worktree","go","terminal-multiplexer","tmux"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d0ugal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-06T15:24:12.000Z","updated_at":"2026-06-30T05:37:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/d0ugal/graith","commit_stats":null,"previous_names":["d0ugal/graith"],"tags_count":100,"template":false,"template_full_name":null,"purl":"pkg:github/d0ugal/graith","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0ugal%2Fgraith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0ugal%2Fgraith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0ugal%2Fgraith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0ugal%2Fgraith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d0ugal","download_url":"https://codeload.github.com/d0ugal/graith/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0ugal%2Fgraith/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35045926,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","cli","git-worktree","go","terminal-multiplexer","tmux"],"created_at":"2026-06-06T22:01:07.208Z","updated_at":"2026-07-02T12:00:56.546Z","avatar_url":"https://github.com/d0ugal.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# graith\n\n**Run a fleet of AI coding agents in parallel — each in its own git worktree, each in a session that outlives your terminal.**\n\ngraith is a terminal multiplexer built for AI coding agents (Claude, Codex, OpenCode, Cursor, Agy). Spin up an agent per task, let them work isolated and unattended, and jump between them with a tmux-style prefix key. A long-lived daemon owns the sessions, so closing your terminal — or losing your SSH connection — doesn't stop the work.\n\n**graith** (Scots) — *noun:* equipment, tools, gear for a specific trade. *verb:* to make ready, prepare, equip. Your agents, graithed and ready to work.\n\n📖 **[Documentation](https://d0ugal.github.io/graith/)** — full guide, CLI reference, configuration, and architecture.\n\n## Why\n\nRunning several agents at once shouldn't mean juggling terminal tabs and stepping on your own branches. graith gives you:\n\n- **Isolation** — every agent gets its own git worktree and branch, so parallel work never collides\n- **Persistence** — a daemon owns the PTYs; sessions survive terminal closures, daemon restarts, and SSH drops\n- **Switching** — hop between agents instantly with a tmux-style prefix key\n- **Visibility** — see every session at a glance, with a \"Needs Attention\" view that surfaces what's blocked or waiting\n- **Coordination** — agents message each other over pub/sub, and you drive them remotely with `type`, `logs`, and a live dashboard\n\nIt owns the PTY, manages the worktrees, and otherwise gets out of your way.\n\n## Install\n\nThe binary is called `gr`.\n\n### Homebrew\n\n```bash\nbrew install d0ugal/tap/graith\n```\n\n### Debian / Ubuntu (apt)\n\nAdd the signing key and repository once, then install with `apt-get`:\n\n```bash\n# add the signing key\ncurl -fsSL https://d0ugal.github.io/graith-repo/gpg/graith.gpg \\\n  | sudo tee /usr/share/keyrings/graith.gpg \u003e /dev/null\n\n# add the repo (signed-by pins it to our key only)\necho \"deb [signed-by=/usr/share/keyrings/graith.gpg] \\\nhttps://d0ugal.github.io/graith-repo/deb stable main\" \\\n  | sudo tee /etc/apt/sources.list.d/graith.list\n\nsudo apt-get update\nsudo apt-get install graith\n```\n\n`apt-get upgrade` then picks up new releases automatically.\n\n### Fedora / RHEL (dnf)\n\n```bash\nsudo tee /etc/yum.repos.d/graith.repo \u003c\u003c'EOF'\n[graith]\nname=graith\nbaseurl=https://d0ugal.github.io/graith-repo/rpm\nenabled=1\ngpgcheck=1\ngpgkey=https://d0ugal.github.io/graith-repo/gpg/graith.asc\nEOF\n\nsudo dnf install graith\n```\n\n`dnf upgrade` picks up new releases.\n\n### From a release\n\nDownload a prebuilt binary for your platform from the [releases page](https://github.com/d0ugal/graith/releases), extract it, and put `gr` on your `$PATH`.\n\nOn Debian/Ubuntu, Fedora/RHEL and Alpine you can instead grab a prebuilt\n`.deb`, `.rpm` or `.apk` package for linux `amd64` or `arm64` (package name\n`graith`, binary `gr`, with shell completions installed) from the same\n[releases page](https://github.com/d0ugal/graith/releases) and install it\nmanually:\n\n```bash\n# Debian / Ubuntu\nsudo dpkg -i graith_*_linux_amd64.deb\n\n# Fedora / RHEL\nsudo rpm -i graith_*_linux_amd64.rpm\n\n# Alpine\nsudo apk add --allow-untrusted graith_*_linux_amd64.apk\n```\n\n### go install\n\n```bash\ngo install github.com/d0ugal/graith/cmd/graith@latest\n```\n\n\u003e `go install` names the binary after the package directory, so this produces a binary called `graith`. Rename it to `gr` (or symlink it) to match the rest of these docs:\n\u003e ```bash\n\u003e mv \"$(go env GOPATH)/bin/graith\" \"$(go env GOPATH)/bin/gr\"\n\u003e ```\n\n### From source\n\n```bash\ngit clone https://github.com/d0ugal/graith\ncd graith\nmake build      # produces ./gr\n```\n\n## Quick Start\n\n```bash\n# Create a new session (auto-starts daemon, creates worktree)\ngr new fix-auth-bug\n\n# Create with a specific agent\ngr new refactor-api --agent codex\n\n# Create with an initial prompt\ngr new fix-tests --prompt \"the auth tests are flaky, find out why\"\n\n# Create in the background without attaching\ngr new long-task --background\n\n# List all sessions\ngr list\n\n# Attach to a session (or show picker if no name given)\ngr attach fix-auth-bug\ngr    # bare gr opens the session picker\n\n# Inside a session (prefix is ctrl+b):\n#   ctrl+b w    → session picker overlay\n#   ctrl+b d    → detach\n#   ctrl+b s    → open shell in the worktree\n#   ctrl+b n/p  → next / previous session\n#   ctrl+b l    → last (most recently attached) session\n#   ctrl+b c    → create a new session\n#   ctrl+b f    → fork the current session\n#   ctrl+b r    → restart a stopped session\n#   ctrl+b ctrl+b → send a literal ctrl+b\n\n# Rename / delete\ngr rename fix-auth-bug auth-rewrite\ngr delete auth-rewrite\n```\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `gr` | Attach (shows session picker if multiple) |\n| `gr new \u003cname\u003e` | Create a new agent session |\n| `gr list` (`ls`) | List all sessions |\n| `gr attach [name]` (`a`) | Attach to a session |\n| `gr stop \u003cname\u003e` | Stop a running session (keeps the worktree); `--children` stops descendants |\n| `gr restart \u003cname\u003e` | Restart a stopped session |\n| `gr delete \u003cname\u003e` (`rm`) | Delete a session and its worktree; `--children` deletes descendants |\n| `gr rename \u003cold\u003e \u003cnew\u003e` | Rename a session |\n| `gr fork \u003csource\u003e \u003cname\u003e` | Fork a session (new worktree + agent conversation history) |\n| `gr info` | Show info for the current session (when inside a worktree) |\n| `gr logs \u003cname\u003e` (`l`) | Show a session's output without attaching |\n| `gr type \u003cname\u003e \u003ctext\u003e` (`t`) | Type text into a session's stdin |\n| `gr status [session] \u003ctext\u003e` | Set a status summary visible in the session picker |\n| `gr msg ...` (`m`) | Inter-agent messaging — see below |\n| `gr dashboard` | Live-updating dashboard of all sessions |\n| `gr approvals` | List sessions waiting for approval |\n| `gr doctor` (`doc`) | Health checks and diagnostics |\n| `gr daemon ...` (`d`) | Manage the daemon — see below |\n| `gr config ...` | Manage configuration (`show`, `diff`, `reset`) |\n| `gr mcp` | Run graith as an MCP tool server (stdio) |\n| `gr completion \u003cshell\u003e` | Generate a shell completion script |\n| `gr version` | Print version information |\n\nGlobal flags: `--config \u003cpath\u003e` to point at a non-default config file, `--json` for machine-readable output, and `--agent-mode` to force agent-friendly behavior (auto-enables `--json`). Agent mode is also auto-detected when running inside a graith session or other AI agent environment.\n\n### `gr new`\n\n```bash\ngr new \u003cname\u003e [flags]\n```\n\n| Flag | Description |\n|------|-------------|\n| `--agent \u003cname\u003e` | Agent to run (defaults to `default_agent` from config) |\n| `--base \u003cbranch\u003e` | Base branch to fork the worktree from (defaults to the repo default branch) |\n| `-C, --repo \u003cpath\u003e` | Path to the git repo (defaults to the current directory) |\n| `--no-repo` | Create a session with no git repo or worktree |\n| `--in-place` | Run agent directly in the repo without creating a worktree |\n| `--allow-concurrent` | Allow multiple in-place sessions on the same repo |\n| `--share-worktree \u003csession\u003e` | Share another session's worktree (read-only) |\n| `--background` | Create the session without attaching to it |\n| `-p, --prompt \u003ctext\u003e` | Send an initial prompt to the agent on startup |\n| `--prompt-file \u003cpath\u003e` | Read the initial prompt from a file |\n| `-m, --model \u003cname\u003e` | Model for the agent to use (expands `{model}` in agent args) |\n\n### `gr daemon`\n\nThe daemon auto-starts on the first command. Manage it explicitly with:\n\n| Command | Description |\n|---------|-------------|\n| `gr daemon start` | Start the daemon |\n| `gr daemon stop` | Stop the daemon |\n| `gr daemon restart` | Restart, preserving live sessions via exec (`--force` for a clean stop/start that kills sessions) |\n| `gr daemon reload` | Reload config without restarting |\n\nAfter rebuilding `gr`, run `gr daemon restart` to pick up the new daemon binary.\n\n## Inter-agent messaging\n\nSessions — and you — can communicate over a SQLite-backed pub/sub system. Each agent process gets `GRAITH_SESSION_ID`/`GRAITH_SESSION_NAME` set, so `gr msg` automatically knows who is sending.\n\n| Command | Description |\n|---------|-------------|\n| `gr msg pub -t \u003ctopic\u003e \u003cbody\u003e` | Publish a message to a stream |\n| `gr msg send \u003csession\u003e \u003cbody\u003e` | Send to a session's inbox; `--children`/`--parent` for tree comms |\n| `gr msg sub -t \u003ctopic\u003e` | Read messages from a stream |\n| `gr msg ack -t \u003ctopic\u003e` | Acknowledge all messages in a stream |\n| `gr msg topics` | List streams with total/unread counts |\n\n```bash\n# Publish findings to a topic\ngr msg pub --topic code-review \"Found a race condition in handler.go:245\"\n\n# Read unread messages from a topic\ngr msg sub --topic code-review\n\n# Show all messages (not just unread)\ngr msg sub --topic code-review --all\n\n# Block until the next message arrives\ngr msg sub --topic code-review --wait\n\n# Follow a stream continuously, acking as you go\ngr msg sub --topic code-review --follow --ack\n\n# Message another session directly (types a notification into it unless --quiet)\ngr msg send fix-auth-bug \"the tests are green now, rebase on main\"\n```\n\n`pub`/`send` accept `--file` to read the body from a file, and `--thread`/`--reply-to` for threaded conversations. `sub` accepts `--thread` to filter to one thread.\n\n```bash\n# From inside a session, message all descendant sessions\ngr msg send --children \"rebase on main and re-run tests\"\n\n# From a child session, message the parent\ngr msg send --parent \"tests are green, ready for review\"\n```\n\n## Status summaries\n\nAgents can report what they're doing with `gr status`. The summary appears in the session picker overlay (ctrl+b w) and in `gr list`.\n\n```bash\n# Set status (auto-detects session when inside one)\ngr status \"Exploring code\"\ngr status \"Waiting for CI\"\ngr status \"Done\"\n\n# Set with a custom TTL for long-running waits\ngr status --ttl 30m \"Waiting for CI\"\n\n# Clear explicitly\ngr status --clear\n\n# Set from outside the session\ngr status my-session \"Reviewing PR\"\n```\n\nStatuses auto-expire when the agent is actively producing output but hasn't updated the status (default 5 minutes). When idle, the status fades but remains visible — so \"Done\" on a stopped session stays put.\n\nThe session picker also auto-derives a summary from hook reports (e.g. \"Using Bash\", \"Using Edit\") when no explicit status is set.\n\nConfigure the default TTL in `config.toml`:\n\n```toml\n[status]\nttl = \"5m\"    # default\n```\n\n## Driving sessions remotely\n\n```bash\n# Type text into a running session (appends a newline by default)\ngr type fix-auth-bug \"/help\"\ngr type fix-auth-bug --no-newline \"y\"\n\n# Watch a session's output without attaching\ngr logs fix-auth-bug --follow\ngr logs fix-auth-bug --lines 500\n\n# See which sessions are blocked waiting for you to approve something\ngr approvals\n\n# A live TUI dashboard of every session (attach/stop/delete/resume inline)\ngr dashboard\n```\n\n## MCP server\n\n`gr mcp` runs graith as a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio, exposing session management as tools: `list_sessions`, `session_status`, `create_session`, `publish_message`, `read_messages`, and `subscribe`. This lets an agent manage other graith sessions as part of its own tool set.\n\n## Shell completion\n\n```bash\n# bash\nsource \u003c(gr completion bash)\n\n# zsh\ngr completion zsh \u003e \"${fpath[1]}/_gr\"\n\n# fish\ngr completion fish | source\n```\n\n`powershell` is also supported.\n\n## Architecture\n\n```\n┌──────────┐     Unix Socket      ┌──────────┐     PTY      ┌─────────┐\n│ gr (CLI) │ ◄──── frames ──────► │ graithd  │ ◄──────────► │ claude  │\n│  client  │   control + data     │  daemon  │              │ codex   │\n└──────────┘                      └──────────┘              │ opencode│\n                                       │                    └─────────┘\n                                  state.json\n                                  (persisted)\n```\n\n- **Daemon** (`graithd`) — owns PTYs, manages state, multiplexes connections\n- **Client** (`gr`) — stateless, connects over a Unix socket, auto-starts the daemon\n- **Protocol** — 5-byte framed multiplexing: `[channel:1][length:4][payload:N]`\n  - Channel `0x00`: JSON control messages, envelope `{\"type\":\"...\",\"payload\":{...}}`\n  - Channel `0x01`: raw PTY data\n\n## Sandbox\n\ngraith can wrap agent processes in a deny-by-default OS sandbox. This lets you run agents with their \"skip permissions\" flags (e.g. `--dangerously-skip-permissions` for Claude, `--dangerously-bypass-approvals-and-sandbox` for Codex) while confining them at the kernel level. Two backends are available:\n\n| Backend | Platforms | Primitive |\n|---------|-----------|-----------|\n| `safehouse` | macOS only | `sandbox-exec` / Seatbelt ([safehouse](https://github.com/eugene1g/agent-safehouse)) |\n| `nono` | **Linux + macOS** | [nono](https://github.com/nolabs-ai/nono): Landlock + seccomp on Linux, Seatbelt on macOS |\n\nSandboxing is **config-only** — there are no CLI flags to enable or disable it. This prevents a sandboxed agent from spawning a child agent that escapes the sandbox (Landlock/Seatbelt restrictions are inherited by descendants).\n\n\u003e **Migration (pre-1.0 breaking change):** `backend` is now **required** when `sandbox.enabled = true` — there is no default. To keep existing behaviour, **add `backend = \"safehouse\"`** to your `[sandbox]` block. On Linux, use `backend = \"nono\"`. Enabling the sandbox without a backend fails closed with an actionable error; `gr doctor` flags it.\n\n### Setup\n\n**safehouse (macOS):** `brew install eugene1g/safehouse/agent-safehouse`\n\n**nono (Linux/macOS):** `brew install nono` (or `curl -fsSL https://nono.sh/install.sh | sh`). nono needs Linux kernel 5.13+ for Landlock (practical floor 5.14+); on macOS it uses Seatbelt. graith enforces a minimum nono version.\n\nVerify with `gr doctor`, then configure:\n\n```toml\nallowed_repo_paths = [\"~/Code\"]         # restrict which repos the daemon will create sessions in\n\n[sandbox]\nenabled  = true\nbackend  = \"nono\"                       # REQUIRED: \"safehouse\" (macOS) or \"nono\" (Linux/macOS)\nfeatures = [\"ssh\"]                      # feature gates (see caveats below)\nread_dirs  = [\"~/Code\"]                 # additional read-only paths\nwrite_dirs = []                         # additional read-write paths\n\n[agents.claude]\ncommand     = \"claude\"\nargs        = [\"--dangerously-skip-permissions\", \"--session-id\", \"{agent_session_id}\"]\nresume_args = [\"--dangerously-skip-permissions\", \"--resume\", \"{agent_session_id}\"]\n```\n\n### How it works\n\nWhen `sandbox.enabled = true`, the daemon resolves the merged policy, expands `~`/globs, and wraps the agent with the selected backend.\n\n**safehouse** runs `safehouse wrap` (macOS `sandbox-exec`): denies file access by default, allows the worktree + `read_dirs`/`write_dirs`, strips the environment to an allowlist, and gates `features`.\n\n**nono** generates a per-session JSON profile and runs `nono run --profile \u003cfile\u003e -- \u003cagent\u003e`. The profile `extends: \"default\"` (inheriting nono's audited credential/shell-history deny groups), maps the worktree and `write_dirs` to `filesystem.allow` (read+write — never nono's write-only `filesystem.write`), `read_dirs` to read-only, grants read on the agent binary's directory (nono does not auto-grant it), and maps the **environment to an allowlist** (`environment.allow_vars`, including `PATH`/`HOME`/`GRAITH_*`) so host secrets aren't leaked — nono otherwise inherits all env. Read-only paths that fall under nono's default-writable `/tmp`/`$TMPDIR` are re-denied so the read-only guarantee holds.\n\nThe sandbox **fails closed**: if enabled but the backend can't enforce (no backend chosen, binary missing, nono below the minimum version, or a Linux kernel too old for Landlock), session creation is refused. A Linux kernel with Landlock filesystem support but no network-filtering ABI runs in a *degraded* mode (filesystem confinement still holds, but see network below). `gr doctor` reports all of this.\n\n### Network (nono only)\n\nBy default agents keep unrestricted outbound network (matching nono's default). You can add an egress policy under `[sandbox.network]`, mapped onto nono's profile `network` section:\n\n```toml\n[sandbox.network]\nblock = true                                # deny all outbound network\n# or, instead of blocking everything, restrict to an allowlist:\nallow_domains = [\"github.com\", \"https://api.anthropic.com/**\"]\n```\n\n- `block = true` → `network.block` (no outbound access at all).\n- `allow_domains` → `network.allow_domain` (nono runs its L7 filtering proxy and only these hosts/URL globs are reachable). A plain hostname allows the whole host; a URL glob restricts to matching endpoints.\n\nNetwork filtering needs **Landlock ABI v4 (Linux kernel 6.7+)**. If a network policy is requested on a kernel that can only do filesystem enforcement, the sandbox **fails closed** — graith refuses rather than pretend to block egress. `safehouse` has no network primitive, so setting a network policy with `backend = \"safehouse\"` also fails closed (use `nono` for network filtering). A network policy can be set globally or per-agent (an agent's `[sandbox.network]` replaces the global one wholesale).\n\n### Feature gate caveats\n\n`features` map differently per backend. Under **nono**: `ssh` grants the `$SSH_AUTH_SOCK` agent socket (socket only; raw `~/.ssh` keys are not granted); `process-control` is a **no-op on its own** (nono's default already permits same-sandbox signals, whereas it gates under safehouse) — set `signal_mode = \"isolated\"` (below) to make it actually gate signalling under nono; any unmapped feature (e.g. `clipboard`) is **warned and ignored**, not silently dropped. nono has no clipboard capability and graith defines no clipboard semantics, so `clipboard` stays a warned no-op.\n\n### Process isolation: `signal_mode` (nono only)\n\n`signal_mode` controls whether the sandboxed process may signal other processes. It maps to nono's `security.signal_mode`:\n\n```toml\n[sandbox]\nsignal_mode = \"isolated\"                    # \"isolated\" | \"allow_same_sandbox\" (nono default) | \"allow_all\"\n```\n\nSetting `signal_mode = \"isolated\"` makes graith's `process-control` feature meaningful under nono (the process can no longer signal anything outside its own sandbox). Leaving it unset inherits nono's base-profile default (`allow_same_sandbox`). `safehouse` ignores this field.\n\n### Debugging denials: `gr sandbox why`\n\n`gr sandbox why` explains whether a given access would be allowed or denied under your configured policy, without launching an agent. It builds the nono profile graith would generate and asks nono's policy oracle:\n\n```bash\ngr sandbox why --path ~/.ssh/id_rsa --op read     # denied (deny_credentials)\ngr sandbox why --path ~/Code/shared --op write    # denied if read-only read_dir\ngr sandbox why --host github.com --port 443        # network reachability\ngr sandbox why --agent codex --path /etc/hosts --op read   # merged per-agent policy\n```\n\n`--op` is `read`, `write`, or `readwrite`; add `--json` for machine-readable output. This targets the **`nono` backend only** (the sole backend with a policy oracle).\n\n### Per-agent overrides\n\nEach agent can extend or disable the global sandbox config:\n\n```toml\n[sandbox]\nenabled  = true\nbackend  = \"nono\"\nfeatures = [\"ssh\"]\n\n[agents.claude.sandbox]\nwrite_dirs = [\"~/.claude\"]             # agent-specific write access\n\n[agents.codex.sandbox]\ndisabled = true                         # opt this agent out of sandboxing\n```\n\n`backend`, `command`, `features`, `signal_mode`, `network`, and directories all merge (global + agent), with the agent's `backend`/`command`/`signal_mode`/`network` taking precedence (an agent's `network` block replaces the global one wholesale). Setting `disabled = true` on an agent overrides `enabled = true` on the global config.\n\n### Path restrictions\n\n`allowed_repo_paths` limits which directories the daemon will accept for `--repo` / `-C`. If set, any repo path outside these prefixes is rejected. Paths support `~` expansion and are resolved to absolute paths before comparison.\n\n```toml\nallowed_repo_paths = [\"~/Code\", \"~/Work\"]\n```\n\nWhen empty (the default), any repo path is accepted.\n\n## Configuration\n\nConfig lives at `~/.config/graith/config.toml` (or `$XDG_CONFIG_HOME/graith/config.toml`). All fields are optional — sensible defaults are provided. The block below shows common options at their default values. Run `gr config show` for the full effective config.\n\n```toml\ndefault_agent   = \"claude\"              # agent used when --agent isn't given\ngithub_username = \"\"                    # used by {username} in branch_prefix\nbranch_prefix   = \"{username}/graith\"   # template for new branch names\nfetch_on_create = true                  # fetch origin before creating a worktree\n# allowed_repo_paths = [\"~/Code\"]       # restrict which repos the daemon allows (empty = any)\n\n[sandbox]\nenabled    = false                      # wrap agents in an OS sandbox\n# backend  = \"nono\"                     # REQUIRED when enabled: \"safehouse\" (macOS) | \"nono\" (Linux/macOS)\n# command  = \"nono\"                     # path to the backend binary (default: the backend name)\n# features = [\"ssh\", \"process-control\"] # feature gates (mapping differs per backend; see the Sandbox section)\n# read_dirs  = []                       # additional read-only paths for sandboxed agents\n# write_dirs = []                       # additional read-write paths for sandboxed agents\n# signal_mode = \"isolated\"              # nono only: \"isolated\" | \"allow_same_sandbox\" | \"allow_all\"\n\n# [sandbox.network]                     # nono only; needs Landlock ABI v4 (kernel 6.7+)\n# block = true                          # deny all outbound network\n# allow_domains = [\"github.com\"]        # OR restrict to a proxy allowlist (host or URL glob)\n\n[status_bar]\nenabled  = true                         # show a status bar while attached\nposition = \"bottom\"\n\n[notifications]\nenabled     = true                      # desktop notifications\non_approval = true                      # notify when a session needs approval\non_stopped  = false                     # notify when a session stops\ncommand     = \"\"                        # custom notification command (optional)\n\n[approvals]\nmode    = \"prompt\"                      # \"prompt\" (ask the agent) or \"notify\" (just notify)\ntimeout = \"10m\"                         # how long to wait for an approval decision\n\n[messages]\nmax_age        = \"\"                     # prune messages older than e.g. \"7d\" / \"168h\" (empty = keep)\nmax_per_stream = 0                      # cap messages per stream (0 = unlimited)\n\n[keybindings]\nprefix         = \"ctrl+b\"               # prefix key\nnew_session    = \"c\"                    # create a session\nfork_session   = \"f\"                    # fork the current session\ndelete_session = \"x\"                    # delete a session\ndetach         = \"d\"                    # detach\nsession_list   = \"w\"                    # open the session picker overlay\nnext_session   = \"n\"                    # next session\nprev_session   = \"p\"                    # previous session\nlast_session   = \"l\"                    # last (most recently attached) session\nresume_session = \"R\"                    # resume a stopped session (config; passthrough uses 'r')\nrename_session = \",\"                    # rename\nsearch         = \"/\"                    # filter sessions\nscroll_mode    = \"[\"                    # enter scroll mode\nshell          = \"s\"                    # open a shell in the worktree\n\n# Each agent is configured under [agents.\u003cname\u003e]. The five below ship by default.\n[agents.claude]\ncommand     = \"claude\"\nargs        = [\"--session-id\", \"{agent_session_id}\"]\nresume_args = [\"--resume\", \"{agent_session_id}\"]\nfork_args   = [\"--resume\", \"{fork_source_agent_session_id}\", \"--fork-session\", \"--session-id\", \"{agent_session_id}\"]\n# env         = { KEY = \"value\" }       # extra env for the agent process (optional)\n# idle_timeout = \"1h\"                   # stop after idle (defaults to 1h if resume_args set)\n# [agents.claude.sandbox]              # per-agent sandbox overrides (merged with global)\n# features  = [\"clipboard\"]\n# write_dirs = [\"~/.claude\"]\n\n[agents.codex]\ncommand     = \"codex\"\nargs        = []\nresume_args = [\"resume\", \"--last\"]\nfork_args   = [\"fork\", \"{fork_source_agent_session_id}\"]\n\n[agents.cursor]\ncommand     = \"agent\"\nargs        = []\nresume_args = [\"resume\"]\n\n[agents.opencode]\ncommand     = \"opencode\"\nargs        = []\nresume_args = [\"--session\", \"{agent_session_id}\"]\n\n[agents.agy]\ncommand     = \"agy\"\nargs        = []\nresume_args = [\"--conversation\", \"{agent_session_id}\"]\n```\n\n### Template variables\n\nThese are substituted in agent `args`, `resume_args`, and `fork_args`. Only `{username}` is available in `branch_prefix`.\n\n| Variable | Expands to |\n|----------|-----------|\n| `{agent_session_id}` | the agent session ID (used for `--session-id` / `--resume`) |\n| `{session_id}` | the unique session ID |\n| `{session_name}` | the session name |\n| `{username}` | `github_username` (or the system username) |\n| `{worktree_path}` | absolute path to the session worktree |\n| `{model}` | the model passed via `--model` (empty if not set) |\n| `{fork_source_agent_session_id}` | agent session ID of the forked source (empty if not a fork) |\n\n## Keybindings\n\n### While attached (passthrough)\n\nPress the prefix (`ctrl+b`), then:\n\n| Key | Action |\n|-----|--------|\n| `w` | Open the session picker overlay |\n| `d` | Detach (leave the agent running) |\n| `s` | Open a shell in the worktree |\n| `c` | Create a new session |\n| `f` | Fork the current session |\n| `n` / `p` | Next / previous session |\n| `l` | Last (most recently attached) session |\n| `r` | Restart a stopped session |\n| `a` | Open the approvals overlay |\n| `,` | Rename the session |\n| `x` | Delete the session |\n| `ctrl+b` | Send a literal prefix byte to the agent |\n\n### Session picker overlay\n\n| Key | Action |\n|-----|--------|\n| `enter` | Attach to the highlighted session |\n| `j` / `k` (or arrows) | Move the cursor |\n| `h` / `l` (or left/right) | Cycle view: All → Needs Attention → Active |\n| `n` / `p` | Next / previous session |\n| `/` | Filter by name |\n| `x` then `y` | Delete (with confirmation) |\n| `q` / `esc` | Close the overlay |\n\n**Views:**\n- **All** — every session, grouped by repo (default)\n- **Needs Attention** — sessions waiting for approval, errored, idle, or stopped with uncommitted/unpushed changes, sorted by time in current state (oldest first)\n- **Active** — running sessions only, sorted newest first\n\n### Dashboard (`gr dashboard`)\n\n| Key | Action |\n|-----|--------|\n| `enter` / `a` | Attach to the highlighted session |\n| `j` / `k` (or arrows) | Move the cursor |\n| `s` | Stop the session (with confirmation) |\n| `x` / `d` | Delete the session (with confirmation) |\n| `r` | Resume a stopped session |\n| `q` / `ctrl+c` | Quit |\n\n## Git worktree lifecycle\n\nWhen you create a session:\n\n1. Fetches latest from origin (when `fetch_on_create` is true)\n2. Creates a branch `\u003cbranch_prefix\u003e/\u003csession-name\u003e-\u003csession-id\u003e` from the base branch\n3. Creates a worktree at `~/.local/share/graith/worktrees/\u003crepo-name\u003e/\u003crepo-hash\u003e/\u003csession-id\u003e/`\n4. Starts the agent in that worktree\n\nWhen you stop a session, the agent process is killed but the worktree and branch are kept (resume restarts the agent in place). When you delete a session, the process is killed, the worktree is removed, and the branch is deleted.\n\n## Environment variables\n\nThe daemon sets these in every agent process:\n\n| Variable | Value |\n|----------|-------|\n| `GRAITH_SESSION_ID` | unique session ID |\n| `GRAITH_SESSION_NAME` | human-readable session name |\n| `GRAITH_AGENT_TYPE` | agent type (e.g. `claude`, `codex`) |\n| `GRAITH_WORKTREE_PATH` | absolute path to the worktree |\n| `GRAITH_REPO_PATH` | absolute path to the source repository (canonical) |\n| `GRAITH_TMPDIR` | per-repo temporary directory (persists across sessions) |\n| `TMPDIR` | set to `GRAITH_TMPDIR` so `mktemp` etc. use the per-repo tmp dir |\n\n`gr shell` additionally exports `GRAITH_WORKTREE`. `gr msg` reads `GRAITH_SESSION_ID`/`GRAITH_SESSION_NAME` to identify the sender automatically.\n\nSet `GR_AGENT_MODE=1` to force agent mode (auto-JSON) or `GR_AGENT_MODE=0` to disable auto-detection.\n\n## File locations\n\ngraith follows the XDG base directory spec:\n\n| Path | Contents |\n|------|----------|\n| `~/.config/graith/config.toml` | configuration |\n| `~/.local/share/graith/state.json` | persisted session state |\n| `~/.local/share/graith/messages.sqlite` | inter-agent message store |\n| `~/.local/share/graith/daemon.log` | daemon log (slog, JSON) |\n| `~/.local/share/graith/worktrees/\u003crepo\u003e/\u003chash\u003e/\u003cid\u003e/` | session worktrees |\n| `$XDG_RUNTIME_DIR/graith/graith.sock` | Unix control socket |\n| `$XDG_RUNTIME_DIR/graith/graith.pid` | daemon PID file |\n\n## Development\n\n```bash\n# Build (binary is ./gr)\nmake build            # or: go build -o gr ./cmd/graith\n\n# Test\ngo test ./...\ngo test -race ./...   # CI runs the race detector\n\n# Lint (Docker-based golangci-lint)\nmake lint             # run with --fix\nmake lint-only        # check only\nmake fmt              # format\n\n# Run\n./gr doctor\n```\n\nAll packages live under `internal/` — there is no public API. See [`AGENTS.md`](AGENTS.md) for a package-by-package map and guidance on using graith to develop graith.\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0ugal%2Fgraith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd0ugal%2Fgraith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0ugal%2Fgraith/lists"}