Daemon management

gr daemon start

Start the daemon — normally automatic and rarely needed.

gr daemon stop

Stop the daemon gracefully. On a supported packaged macOS installation this leaves the Graith user service registered but dormant. It does not restart at login or after a crash; the next ordinary gr command starts it on demand. Graith waits for the daemon’s normal shutdown budget rather than the shorter startup budget. If the daemon still does not exit, the error includes the daemon PID and exact recovery commands. Stopping a daemon is not the same as removing its background-item registration. Daemon stop, restart, service removal, and replacement are user-only controls; commands run from an agent session are refused. Session agents can still use ordinary commands such as gr stop <session>. Managed nono sandboxes restrict signalling to the sandbox, so clearing session environment markers cannot grant direct host-process signalling authority. Safehouse cannot enforce this signal boundary; use nono when it is required.

gr daemon restart

Restart the daemon, preserving live sessions via exec.

FlagDescription
--forceClean stop/start that kills running sessions; escalates from SIGTERM to diagnostic SIGQUIT and then SIGKILL if shutdown wedges

After rebuilding gr, run gr daemon restart to pick up the new daemon binary. Crossing from the approval-era protocol to the non-interactive Graith protocol is an intentional breaking restart: rather than preserving them, graith gracefully stops the old daemon and all its PTY and headless sessions once it confirms the exact old socket peer has exited and its socket has disappeared — if either check fails, no competing daemon starts. Resume stopped sessions to relaunch under the new security model.

Before it hands off any live terminal, graith checks that the exact replacement binary can adopt every session and understands the current helper handoff. It refuses the attempt while session creation or another lifecycle launch is in progress. Such a refusal leaves the existing daemon and agents running; retry after the reported work finishes. If preparation or exec fails, inheritable terminal descriptors are restored and the existing daemon continues serving. The replacement may use a newer state schema when it has the forward migrations needed to load the running daemon’s exact snapshot. A replacement with an older state schema is rejected as a downgrade, while manifest and adoption protocol versions must still match exactly. Compatibility errors report the bounded numeric target and running versions for each mismatched boundary.

When upgrading from a release with Graith-owned external-tool management, remove the obsolete lifecycle and security keys identified by the new config validation error. The daemon rejects them instead of silently ignoring them. The old daemon drains its managed children, sockets, stderr pipes, and reconnect loops before exec. A live PTY can still be adopted, but any proxy injected into that already-running agent is permanently unavailable. Restart or resume the session to relaunch it without Graith-generated integration arguments or files.

Before migrating older durable state, the replacement writes an exact versioned backup beside state.json. If permissions, disk capacity, or another storage failure prevents that backup, startup and exec adoption stop before migration or session handoff and leave the old state bytes unchanged. Fix the reported storage problem and retry; do not remove the old state file to bypass this safeguard.

Live adoption requires persisted state and the handoff manifest to prove the exact process identity; the manifest records every live process, not just transferable PTYs. Whether that process uses Graith’s sandbox or the agent’s native approval TUI is preserved, not an adoption requirement. Sessions from pre-transition releases — and headless sessions, which have no adoptable PTY — are identity-checked, terminated, and marked stopped rather than left unmanaged.

The replacement arms cleanup before loading configuration, paths, state, or authentication, so an early failure still identity-checks and terminates inherited agents. Once live session adoption is committed, the replacement starts serving, removes the consumed upgrade journal first, and then repairs interrupted deletes, soft-delete kills, stale orphan processes, or scenario-add cleanup; those repairs continue in daemon-owned background work and are logged as startup recovery phases. If preserve is accepted but the replacement isn’t ready after the configured startup wait, graith rechecks the live daemon and falls back to a clean start only after proving the exact process that answered the pre-upgrade handshake has exited — a stale PID file isn’t enough — then checks that result for the requested version and a fresh daemon generation. Otherwise it leaves the possible in-progress replacement alone: retry once startup finishes, or use --force to kill the sessions intentionally.

If a preserve restart cannot drain accepted daemon mutations before its deadline, the error and daemon log identify every active holder by an opaque lease ID, operation, bounded caller identity, and age. Payloads, tokens, and message contents are never included. Wait for the named work to finish and retry; use --force only when intentionally interrupting it.

With --force, graith still first authenticates the daemon socket peer and checks its process identity so it does not signal an unrelated recycled PID. If the daemon ignores the graceful stop budget, graith asks the Go runtime for a SIGQUIT diagnostic dump and then sends SIGKILL to the same verified process. After a forced exit it removes only the stale socket and PID file entries that still match the stopped daemon, then starts a fresh daemon. If even this fails, the error names the PID and gives the manual kill -9 and gr doctor --autofix recovery commands.

Ordinary commands use the same preserved exec path after a daemon/CLI version mismatch. If a managed auto-upgrade cannot prove a fresh daemon generation, the old daemon and sessions keep running; retry with gr daemon restart, then check gr daemon service status and gr daemon service repair if it repeats.

gr daemon reload

Reload configuration on the already-running daemon without starting, restarting, or auto-upgrading it. If the daemon is not reachable, start or restart it explicitly instead. Invalid settings or a non-optional runtime apply failure return an error and leave the previous config generation in place. Remote transport replacement closes the old listener first and stays closed if the replacement fails; the command still succeeds after the config generation is applied and reports remote access as degraded and closed. Fix the setting or dependency, then reload again through the local socket; the command prints this retry guidance whenever it reports remote access as degraded. In remote.mode = "interface", this includes starting tailscaled and rerunning gr daemon reload. See remote hot reload.

macOS user service

On macOS 13 or newer, signed Homebrew and release-tarball installations run the foreground daemon as an app-associated service for the logged-in user. Activity Monitor and System Settings → General → Login Items can therefore attribute it to Graith, rather than to the terminal that ran the first command. The headless Graith.app has no Dock icon or menu-bar UI. It is never installed as a root or system daemon.

The service is demand-started: install and login do not launch it, and there is no unconditional crash restart. Closing Terminal does not stop a healthy daemon. A crash, logout, reboot, or intentional stop leaves the registered job dormant until the next eligible command. If Login Items says Graith requires approval or is disabled, startup fails with guidance and never bypasses that choice with a Terminal-owned process.

The default profile has its own service label. Named profiles lease distinct static labels, so their labels, sockets, PIDs, config, state, tokens, and lifecycle controls cannot collide. Up to 64 named profiles may remain registered at once (running or dormant); remove a dormant profile service to free a slot. A supported package at capacity fails instead of falling back to a directly spawned daemon.

A Homebrew graith-dev package that includes the signed Graith.app uses the same service identity but an isolated dev profile and service slot, so it can coexist with stable Graith. A transitional dev package without that bundle retains the previous direct-spawn behavior rather than installing an unsigned service app. After upgrading a managed dev package, run gr-dev daemon restart; before uninstalling it, run gr-dev daemon service remove. macOS 11/12, source builds, go install, and Linux keep the direct-spawn behavior. gr doctor reports which mode is active. The dev and stable channels do not publish separately named rollback archives; see the native dev canary guidance for supported platform artifacts and channel changes.

gr daemon service status

Show the active profile’s service label, slot, lease state, Service Management status, launchd state, PID, and registered/running bundle generations.

FlagDescription
--all-profilesInclude the default, every named-profile lease, and quarantined slots

Use --json for structured output.

gr daemon service remove

Stop the exact profile daemon, unregister its user service, and release a named profile’s slot only after launchd confirms the job is gone. Config, state, worktrees, tokens, messages, and logs are preserved.

FlagDescription
--all-profilesRemove the default and every registered named-profile service

Run gr daemon service remove --all-profiles before uninstalling a macOS package. An ordinary upgrade must not remove the services; Graith keeps signed, versioned app generations and rotates each dormant registration safely.

gr daemon service repair

Validate the owner-only service receipt and its backup, inspect only Graith’s 65 exact compiled labels and signed cached apps, and repair state that is proven dormant. A valid backup restores the primary. Unknown live, disabled, or indeterminate jobs are quarantined rather than killed or reassigned. Reinstall a signed package first if gr or Graith.app was deleted before service removal; then run this command instead of using wildcard launchctl cleanup.

The managed service starts with a deliberately small environment projection. See [daemon_service] before upgrading if agents depend on SSH_AUTH_SOCK, cloud credentials, or API keys inherited from your terminal.

Agent introspection

gr agent list (alias: gr agent ls)

List the agents configured in the daemon’s effective configuration. The table shows the agent name, whether it is the configured default, the launch command, and any configured provider info keys.

Use --json for the raw catalog:

  {
  "agents": [
    {
      "name": "cursor",
      "command": "agent",
      "info_keys": ["model", "version"]
    }
  ],
  "default_agent": "claude"
}
  

gr agent info <agent> [key]

Run configured provider info commands for an agent. Without [key], graith runs every key under [agents.<agent>.info] in sorted order. With [key], it runs only that key, for example:

  gr agent info cursor model
gr agent info cursor --json
gr agent info cursor --refresh --json
gr agent info cursor --no-cache
  

The daemon runs the configured agent command with the selected info argv in a daemon-owned provider context with the agent’s optional Graith sandbox. It does not require the calling shell to have the provider CLI available. Normal session launch args, prompt injection, hooks, and included-repo flags are not applied.

Successful results are cached in daemon memory for one hour by default, keyed by agent, info key, and effective command configuration. Configure the default with [agent_info].cache_ttl; set it to "0" to disable caching. When global caching is enabled, rich info-command tables can override one key with cache_ttl. The cache is empty after daemon restart. --refresh bypasses the read side and updates the cache on a successful provider command; --no-cache bypasses both cache reads and writes. The two flags are mutually exclusive. Expired entries are not returned. If a provider command fails after expiry, the failure is returned instead of stale output. Concurrent misses or refreshes for the same agent/key share one provider command where possible.

The human-readable single-key form prints the provider stdout directly. With --json, stdout, stderr, cache metadata, exit code, truncation flags, warnings, structured fields, and per-result errors are separate fields:

  {
  "agent": "cursor",
  "results": [
    {
      "key": "model",
      "command": "agent",
      "args": ["--list-models"],
      "format": "model_list",
      "stdout": "auto - Auto\n",
      "models": [
        {
          "id": "auto",
          "description": "Auto"
        }
      ],
      "cache": {
        "enabled": true,
        "hit": false,
        "fetched_at": "2026-07-29T12:00:00Z",
        "expires_at": "2026-07-29T13:00:00Z"
      },
      "exit_code": 0
    }
  ]
}
  

Unknown agents, unknown info keys, and agents without info configuration fail the request. Provider command failures and timeouts are returned on the specific result, then the CLI exits non-zero after printing the plain or JSON output.

Configuration and lookup errors are returned before running probes. Each info command has a 30 second daemon timeout. Captured stdout and stderr are capped at 1 MiB each; JSON results set stdout_truncated or stderr_truncated when a stream exceeds that cap, and plain output prints a truncation marker. Cleanup diagnostics that do not invalidate successful provider output are returned as warnings.

Other commands

gr config show

Print the effective (merged) configuration.

gr config diff

Show changes from built-in defaults.

gr config reset

Write built-in defaults to the config file.

FlagDescription
--forceOverwrite without confirmation

gr completion <shell>

Generate a shell completion script. Supported shells: bash, zsh, fish, powershell.

gr help-tree [COMMAND...]

Print a compact command tree for quickly discovering the CLI. By default it prints text with two-space indentation, branch command descriptions, and leaf command args plus usable non-global flags. Hidden and deprecated commands are omitted.

Pass a command path to inspect only that subtree:

  gr help-tree msg
gr help-tree scenario start
  
FlagDescription
--depth <n>Limit nesting depth; 1 shows the selected command plus direct children, 0 is unlimited

Use --json for the same tree as structured output.

gr version

Print version information.

Hidden/internal commands

Used by graith internally, not intended for direct use:

CommandPurpose
gr report-statusReport agent status (used by hooks)
gr check-inboxCheck unread inbox messages (used by hooks)