Documentation
¶
Index ¶
- Variables
- func DefaultAgentPrompt() string
- func DefaultTOML() []byte
- func Expand(s string, vars TemplateVars) (string, error)
- func ExpandPath(p string) string
- func ExpandPathRelative(p, baseDir string) string
- func ExpandSlice(ss []string, vars TemplateVars) ([]string, error)
- func IncludeEnvVarName(repoBasename string) string
- func LegacyRuntimeDirs() []string
- func ParseDurationWithDays(s string) (time.Duration, error)
- func ResolveConfigPath(explicit string) (path string, exists bool, err error)
- func ResolvePath(p string) string
- func ResolveProfile() (profile string, appName string, err error)
- type Agent
- type Approvals
- type ApprovalsBuiltin
- type Config
- type GitPullConfig
- type Keybindings
- type MCPServerConfig
- type Messages
- type Notifications
- type OrchestratorConfig
- type OrchestratorSandboxConfig
- type Overlay
- type PRWatchConfig
- type Paths
- type RepoConfig
- type SandboxConfig
- type SandboxNetworkConfig
- type StatusBar
- type StatusConfig
- type TemplateVars
- type UnknownKey
- type Watcher
Constants ¶
This section is empty.
Variables ¶
var SandboxSignalModes = []string{"isolated", "allow_same_sandbox", "allow_all"}
SandboxSignalModes are the accepted values for [sandbox] signal_mode. They mirror nono v0.66.0's security.signal_mode enum. Empty is also valid (inherit nono's base-profile default).
Functions ¶
func DefaultAgentPrompt ¶ added in v0.35.0
func DefaultAgentPrompt() string
func DefaultTOML ¶ added in v0.19.0
func DefaultTOML() []byte
func ExpandPath ¶ added in v0.11.0
func ExpandPathRelative ¶ added in v0.66.0
ExpandPathRelative resolves a configured path deterministically: it expands a leading ~/, and resolves a still-relative path against baseDir (the directory holding the config file) rather than the process working directory, then cleans the result. This keeps a value like [approvals.builtin] config resolving to the same absolute path regardless of which directory the daemon or CLI happens to run from. An empty (or whitespace-only) path stays empty so callers can distinguish "unset" from a resolved path.
func ExpandSlice ¶
func ExpandSlice(ss []string, vars TemplateVars) ([]string, error)
func IncludeEnvVarName ¶ added in v0.19.0
func LegacyRuntimeDirs ¶ added in v0.11.0
func LegacyRuntimeDirs() []string
LegacyRuntimeDirs returns paths where older versions stored the socket and PID file (TMPDIR or /tmp fallbacks). Used during startup to detect and clean up an orphaned daemon after the socket location changed.
func ParseDurationWithDays ¶ added in v0.3.0
func ResolveConfigPath ¶ added in v0.66.0
ResolveConfigPath returns the config file that LoadOrDefault(explicit) would read and whether that file exists on disk. When explicit is set it is used verbatim. When empty, resolution mirrors LoadOrDefault: the profile/XDG path, falling back to the legacy macOS path only when the XDG file is absent and no profile is active. Diagnostics (e.g. gr doctor) use this so the reported and inspected file is the same one the CLI/daemon actually load.
func ResolvePath ¶ added in v0.16.5
func ResolveProfile ¶ added in v0.18.0
Types ¶
type Agent ¶
type Agent struct {
Command string `json:"command" toml:"command"`
Args []string `json:"args,omitempty" toml:"args"`
ResumeArgs []string `json:"resume_args,omitempty" toml:"resume_args"`
ForkArgs []string `json:"fork_args,omitempty" toml:"fork_args"`
Env map[string]string `json:"env,omitempty" toml:"env"`
IdleTimeout string `json:"idle_timeout,omitempty" toml:"idle_timeout"`
InjectPrompt *bool `json:"inject_prompt,omitempty" toml:"inject_prompt"`
PreTrustWorkspace *bool `json:"pre_trust_workspace,omitempty" toml:"pre_trust_workspace"`
Sandbox SandboxConfig `json:"sandbox" toml:"sandbox"`
MCPServers map[string]MCPServerConfig `json:"mcp_servers,omitempty" toml:"mcp_servers"`
ValidateModel string `json:"validate_model,omitempty" toml:"validate_model"`
// InterruptCount is how many times the interrupt byte (Ctrl-C, 0x03) is sent
// to interrupt this agent, and InterruptDelayMs is the pause in milliseconds
// between successive sends. Some agent TUIs ignore a single Ctrl-C and need
// two rapid presses to actually interrupt (Claude's TUI wants ~200ms apart),
// so both are configurable per agent. Unset means the built-in defaults
// (count 1, delay 0). See issue #620.
InterruptCount *int `json:"interrupt_count,omitempty" toml:"interrupt_count"`
InterruptDelayMs *int `json:"interrupt_delay_ms,omitempty" toml:"interrupt_delay_ms"`
}
func (Agent) IdleTimeoutDuration ¶
func (Agent) InterruptCountValue ¶ added in v0.66.2
InterruptCountValue returns how many times the interrupt byte (Ctrl-C, 0x03) should be sent to interrupt this agent. Defaults to 1 when unset; a value below 1 is clamped to 1 so an interrupt always sends at least once.
func (Agent) InterruptDelay ¶ added in v0.66.2
InterruptDelay returns the pause between successive interrupt bytes. Defaults to 0 (send back-to-back) when unset; a negative value is treated as 0.
func (Agent) PreTrustWorkspaceEnabled ¶ added in v0.48.0
func (Agent) PromptInjectionEnabled ¶ added in v0.32.0
type Approvals ¶ added in v0.13.0
type Approvals struct {
// Enabled controls whether the PreToolUse approve-request gating hook is
// installed. nil (unset) means disabled: the status/lifecycle hooks are
// still installed but the approval gate is not, because unattended agents
// otherwise see their own tool calls as human-rejected and the OS sandbox
// is the intended guardrail. Set to true to opt back into human approval
// gating.
Enabled *bool `toml:"enabled"`
// Backend selects who makes the automated decision: "" (none — always
// prompt the human), "command"/"external" (delegate to a command over
// graith's JSON contract), "localmost" (the real localmost binary over its
// native protocol), or "builtin" (graith's built-in localmost-compatible
// engine). It is the canonical selector; Mode is the deprecated predecessor.
Backend string `toml:"backend"`
Mode string `toml:"mode"`
AutoPop bool `toml:"auto_pop"`
Timeout string `toml:"timeout"`
Command string `toml:"command"`
Builtin ApprovalsBuiltin `toml:"builtin"`
}
func (Approvals) HookEnabled ¶ added in v0.65.0
HookEnabled reports whether the approve-request PreToolUse hook should be installed. Defaults to false when unset — approval gating is opt-in.
func (Approvals) ResolveBackend ¶ added in v0.64.4
ResolveBackend resolves the effective approvals backend, applying back-compat for the deprecated Mode field. It returns the backend name, a non-empty deprecation message when a legacy Mode value was used (callers log it once), and an error for an unknown backend or a conflicting Mode+Backend pair.
Resolution order:
- If Backend is set, use it. If a legacy Mode is ALSO set and maps to a different backend, that is a hard error (refuse to guess intent).
- Else if Mode is one of command/external/localmost, map it to the "command" backend (historical behaviour) and return a deprecation message. A Mode with no Backend is always a warning, never an error.
- Else, the "prompt" backend (no automation).
func (Approvals) TimeoutDuration ¶ added in v0.13.0
func (Approvals) Validate ¶ added in v0.64.5
Validate checks the [approvals] config for static contradictions that would otherwise only surface as an opaque fail-closed session crash at create time (see #740). It rejects an unknown or conflicting backend/mode (via ResolveBackend) and a command key set for a resolved backend that ignores it. Backend *availability* (command present, localmost binary on PATH, builtin config loadable) is still deferred to session-create by the daemon.
type ApprovalsBuiltin ¶ added in v0.64.4
type ApprovalsBuiltin struct {
// Config is the path to a localmost-format config.json (allow/deny rules).
Config string `toml:"config"`
// Allow and Deny are the inline allow/deny rulesets. Each element is either
// a bare rule string ("@arg @*") or a table with per-rule keys
// (rule/unless/redirect/pipe). They are decoded as []any so both TOML forms
// — an array of strings and an array of tables ([[approvals.builtin.allow]])
// — are accepted, then converted to the localmost schema (see InlineJSON).
Allow []any `toml:"allow"`
Deny []any `toml:"deny"`
// AllowSafeXargs and AskNoninteractive mirror the localmost top-level flags.
// nil means unset (the engine's default of true applies).
AllowSafeXargs *bool `toml:"allowSafeXargs"`
AskNoninteractive *bool `toml:"askNoninteractive"`
}
ApprovalsBuiltin configures the built-in localmost-compatible engine. Rules can be supplied either as a path to an external localmost-format config.json (Config), or inline in config.toml via Allow/Deny/AllowSafeXargs/ AskNoninteractive. The two forms are mutually exclusive (see Approvals.Validate).
func (ApprovalsBuiltin) HasInline ¶ added in v0.66.0
func (b ApprovalsBuiltin) HasInline() bool
HasInline reports whether any inline ruleset field is set. When true, the rules are read from config.toml rather than an external Config file. An empty array (allow = []) defines no rules and does not count as inline, so it does not spuriously conflict with an external Config path.
func (ApprovalsBuiltin) InlineJSON ¶ added in v0.66.0
func (b ApprovalsBuiltin) InlineJSON() ([]byte, error)
InlineJSON renders the inline ruleset as localmost-format config.json bytes, so the existing (tested) localmost parser can compile it. The TOML keys map 1:1 to the localmost JSON schema (allow/deny/allowSafeXargs/askNoninteractive, and per-rule rule/unless/redirect/pipe), so a plain JSON re-encode suffices.
type Config ¶
type Config struct {
DefaultAgent string `toml:"default_agent"`
GitHubUsername string `toml:"github_username"`
BranchPrefix string `toml:"branch_prefix"`
DataDir string `toml:"data_dir"`
FetchOnCreate bool `toml:"fetch_on_create"`
AgentPrompt string `toml:"agent_prompt"`
AllowedRepoPaths []string `toml:"allowed_repo_paths"`
Repos []RepoConfig `toml:"repos"`
StatusBar StatusBar `toml:"status_bar"`
Keybindings Keybindings `toml:"keybindings"`
Notifications Notifications `toml:"notifications"`
Messages Messages `toml:"messages"`
Sandbox SandboxConfig `toml:"sandbox"`
Approvals Approvals `toml:"approvals"`
Status StatusConfig `toml:"status"`
GitPull GitPullConfig `toml:"git_pull"`
PRWatch PRWatchConfig `toml:"pr_watch"`
MCPServers []MCPServerConfig `toml:"mcp_servers"`
Overlay Overlay `toml:"overlay"`
Orchestrator OrchestratorConfig `toml:"orchestrator"`
Agents map[string]Agent `toml:"agents"`
}
func LoadOrDefault ¶
func (*Config) FindRepo ¶ added in v0.18.0
func (c *Config) FindRepo(repoPath string) (RepoConfig, bool)
func (*Config) OrchestratorSandboxMerged ¶ added in v0.46.0
func (c *Config) OrchestratorSandboxMerged(agentName string) SandboxConfig
func (*Config) RepoPathAllowed ¶ added in v0.11.0
type GitPullConfig ¶ added in v0.42.0
func (GitPullConfig) IntervalDuration ¶ added in v0.42.0
func (g GitPullConfig) IntervalDuration() time.Duration
type Keybindings ¶
type Keybindings struct {
Prefix string `toml:"prefix"`
NewSession string `toml:"new_session"`
ForkSession string `toml:"fork_session"`
DeleteSession string `toml:"delete_session"`
Detach string `toml:"detach"`
SessionList string `toml:"session_list"`
NextSession string `toml:"next_session"`
PrevSession string `toml:"prev_session"`
LastSession string `toml:"last_session"`
ResumeSession string `toml:"resume_session"`
RenameSession string `toml:"rename_session"`
Search string `toml:"search"`
ScrollMode string `toml:"scroll_mode"`
Shell string `toml:"shell"`
OrchestratorSession string `toml:"orchestrator_session"`
}
type MCPServerConfig ¶ added in v0.22.0
type MCPServerConfig struct {
Name string `json:"-" toml:"name"`
Command string `json:"command" toml:"command"`
Args []string `json:"args,omitempty" toml:"args,omitempty"`
Env map[string]string `json:"env,omitempty" toml:"env,omitempty"`
Disabled bool `json:"-" toml:"disabled,omitempty"`
Sandbox *bool `json:"-" toml:"sandbox,omitempty"`
SandboxConfig *SandboxConfig `json:"-" toml:"sandbox_config,omitempty"`
}
func MergeMCPServers ¶ added in v0.22.0
func MergeMCPServers(global []MCPServerConfig, overrides map[string]MCPServerConfig) []MCPServerConfig
type Messages ¶ added in v0.3.0
func (Messages) MaxAgeDuration ¶ added in v0.3.0
type Notifications ¶ added in v0.2.0
type OrchestratorConfig ¶ added in v0.42.0
type OrchestratorConfig struct {
Enabled bool `toml:"enabled"`
Agent string `toml:"agent"`
Model string `toml:"model"`
IdleTimeout string `toml:"idle_timeout"`
Prompt string `toml:"prompt"`
PromptFile string `toml:"prompt_file"`
Sandbox OrchestratorSandboxConfig `toml:"sandbox"`
}
func (OrchestratorConfig) AgentName ¶ added in v0.42.0
func (o OrchestratorConfig) AgentName() string
func (OrchestratorConfig) IdleTimeoutDuration ¶ added in v0.42.0
func (o OrchestratorConfig) IdleTimeoutDuration() time.Duration
type OrchestratorSandboxConfig ¶ added in v0.46.0
type PRWatchConfig ¶ added in v0.59.0
type PRWatchConfig struct {
Enabled bool `toml:"enabled"`
NotifyCIFailures bool `toml:"notify_ci_failures"`
NotifyMergeConflicts bool `toml:"notify_merge_conflicts"`
NotifyReviewComments bool `toml:"notify_review_comments"`
NotifyReviewDecisions bool `toml:"notify_review_decisions"`
NotifyPRLifecycle bool `toml:"notify_pr_lifecycle"`
NotifyCIRecovery bool `toml:"notify_ci_recovery"`
PollPending string `toml:"poll_pending"`
PollTerminal string `toml:"poll_terminal"`
PollMerged string `toml:"poll_merged"`
MaxNotificationsPerPR int `toml:"max_notifications_per_pr"`
Debounce string `toml:"debounce"`
}
PRWatchConfig controls the PR & CI awareness loop, which resolves each session's GitHub PR via the gh CLI, polls its CI checks and review comments, and notifies the owning session's inbox on meaningful transitions.
CI failures and review feedback are gated separately because they carry different authority: a CI failure is a machine verdict (safe to act on, default on); a review comment or decision is human intent that may not be actionable (default off).
func (PRWatchConfig) DebounceDuration ¶ added in v0.59.0
func (p PRWatchConfig) DebounceDuration() time.Duration
DebounceDuration is the minimum cooldown between notifications to one session.
func (PRWatchConfig) MaxNotifications ¶ added in v0.59.0
func (p PRWatchConfig) MaxNotifications() int
MaxNotifications returns the per-head-SHA notification cap, defaulting to 10.
func (PRWatchConfig) PollMergedDuration ¶ added in v0.59.0
func (p PRWatchConfig) PollMergedDuration() time.Duration
PollMergedDuration is the sweep interval for merged/closed PRs.
func (PRWatchConfig) PollPendingDuration ¶ added in v0.59.0
func (p PRWatchConfig) PollPendingDuration() time.Duration
PollPendingDuration is the poll interval while a PR has pending/in-progress checks.
func (PRWatchConfig) PollTerminalDuration ¶ added in v0.59.0
func (p PRWatchConfig) PollTerminalDuration() time.Duration
PollTerminalDuration is the poll interval once all checks are terminal (PR still open).
type Paths ¶
type Paths struct {
Profile string
AppName string
ConfigFile string
DataDir string
RuntimeDir string
SocketPath string
PIDFile string
StateFile string
LogDir string
DaemonLog string
MessagesDB string
TmpDir string
}
func ResolvePaths ¶
func (Paths) EnsureDirs ¶
func (Paths) WithDataDir ¶ added in v0.21.0
type RepoConfig ¶ added in v0.18.0
type RepoConfig struct {
Path string `toml:"path"`
AllowConcurrent bool `toml:"allow_concurrent"`
Singleton bool `toml:"singleton"`
Includes []string `toml:"includes"`
}
func (RepoConfig) Validate ¶ added in v0.19.0
func (rc RepoConfig) Validate() error
type SandboxConfig ¶ added in v0.11.0
type SandboxConfig struct {
Enabled bool `json:"enabled" toml:"enabled"`
Disabled *bool `json:"disabled,omitempty" toml:"disabled,omitempty"`
// Backend selects the sandbox backend: "safehouse" (macOS only) or "nono"
// (Linux + macOS). It has NO default — when the sandbox is enabled and
// Backend is unset the daemon fails closed with an actionable error. This
// is a deliberate pre-1.0 behaviour change (see the nono sandbox design doc).
Backend string `json:"backend,omitempty" toml:"backend"`
Command string `json:"command,omitempty" toml:"command"`
// Profile (nono only) is the base profile graith's generated profile
// extends. Empty means nono's built-in "default" (its audited deny groups +
// base system paths). Set it to a maintained registry profile — e.g.
// "always-further/claude" — to inherit that agent's upstream file grants
// (its ~/.claude, ~/.claude.json, versioned binary dir, …) instead of
// hand-listing them via write_files.
//
// nono resolves "extends" by MERGING the base profile with graith's
// generated one. Collection fields (filesystem.allow/read,
// environment.allow_vars, network.allow_domain, …) are UNIONED (append +
// dedup) — graith's grants are added to, not substituted for, the base's;
// only scalar fields (e.g. workdir.access, security.signal_mode) are
// child-overridden. So graith's filesystem grants are always present, but
// graith's env allowlist can only WIDEN the base profile's, it cannot narrow
// it. A base profile that allows extra env vars, network
// domains, set_vars, command policies, or session hooks (which run outside
// the sandbox) therefore relaxes graith's baseline — so a custom profile is
// only as tight as the operator has audited it to be. Choose a trusted,
// least-privilege profile. nono's audited deny groups (deny_credentials, …)
// are marked required and merged into every resolved profile regardless of
// this field, so a custom base cannot silently drop the credential-deny
// baseline. The safehouse backend has no profile concept and ignores it.
Profile string `json:"profile,omitempty" toml:"profile"`
Features []string `json:"features,omitempty" toml:"features"`
ReadDirs []string `json:"read_dirs,omitempty" toml:"read_dirs"`
WriteDirs []string `json:"write_dirs,omitempty" toml:"write_dirs"`
// ReadFiles / WriteFiles grant access to individual files rather than whole
// directories. They exist for paths that can't be expressed as a directory
// grant without over-sharing — most importantly single files that live
// directly in $HOME (e.g. an agent's ~/.claude.json login file), where
// granting the parent directory would expose unrelated secrets (.env, ssh
// keys, tfvars). ReadFiles is read-only; WriteFiles is read+write, mirroring
// the read_dirs / write_dirs convention (where "write" means read+write, not
// nono's write-only mode). They map to the nono profile's
// filesystem.read_file / filesystem.allow_file; the safehouse backend folds
// them into its read-only / read-write path lists.
ReadFiles []string `json:"read_files,omitempty" toml:"read_files"`
WriteFiles []string `json:"write_files,omitempty" toml:"write_files"`
// SignalMode controls whether the sandboxed process may signal other
// processes. It maps to nono's security.signal_mode ("isolated",
// "allow_same_sandbox", "allow_all"). Empty inherits nono's base-profile
// default (allow_same_sandbox). safehouse ignores it. Setting "isolated"
// makes graith's `process-control` semantics meaningful under nono (Phase 1
// left it a no-op). See the nono sandbox design doc §C5.
SignalMode string `json:"signal_mode,omitempty" toml:"signal_mode"`
// Network is an optional egress policy. It maps to the nono profile's
// network section (network.block / network.allow_domain). safehouse has no
// network primitive and only warns. A network policy also raises the
// enforcement floor: nono needs Landlock ABI v4 (kernel 6.7+) to filter
// network, so a requested policy on an older kernel fails closed.
Network *SandboxNetworkConfig `json:"network,omitempty" toml:"network"`
}
func (SandboxConfig) Merge ¶ added in v0.11.0
func (s SandboxConfig) Merge(agent SandboxConfig) SandboxConfig
type SandboxNetworkConfig ¶ added in v0.64.0
type SandboxNetworkConfig struct {
// Block denies all outbound network access (nono is network-allowed by
// default). Maps to network.block = true.
Block bool `json:"block,omitempty" toml:"block"`
// AllowDomains is the proxy allowlist. Maps to network.allow_domain. When
// set, nono runs its L7 filtering proxy and only these domains are
// reachable. Entries are plain hostnames or URL globs.
AllowDomains []string `json:"allow_domains,omitempty" toml:"allow_domains"`
}
SandboxNetworkConfig is graith's egress policy. It maps directly onto nono v0.66.0's profile network section: Block -> network.block, AllowDomains -> network.allow_domain (an L7 proxy allowlist; a plain hostname allows the host, a URL glob restricts to matching endpoints).
func (*SandboxNetworkConfig) IsSet ¶ added in v0.64.0
func (n *SandboxNetworkConfig) IsSet() bool
IsSet reports whether this network policy requests any egress restriction. A nil or empty config requests nothing (matches nono's allow-by-default).
type StatusConfig ¶ added in v0.32.0
type StatusConfig struct {
TTL string `toml:"ttl"`
}
func (StatusConfig) TTLDuration ¶ added in v0.32.0
func (s StatusConfig) TTLDuration() time.Duration
type TemplateVars ¶
type UnknownKey ¶ added in v0.66.0
type UnknownKey struct {
// Table is the dotted parent-table path, e.g. "agents.claude.sandbox".
// Empty for top-level keys.
Table string
// Name is the unrecognised leaf key, e.g. "read_dir".
Name string
// Suggestion is the closest known key in the same table, or "" if none is
// close enough to be worth a "did you mean".
Suggestion string
}
UnknownKey is a config key that graith's schema does not recognise. It is a diagnostic aid (surfaced by `gr doctor`), not a load error: the runtime load stays lenient so an older daemon won't refuse a config written for a newer graith, and a typo silently drops the key rather than bricking startup. See issue #720.
func UnknownKeys ¶ added in v0.66.0
func UnknownKeys(path string) ([]UnknownKey, error)
UnknownKeys parses the TOML at path and reports keys that don't map to any field in the Config schema — typos (read_dir vs read_dirs), keys under the wrong table, or options from a newer graith than this binary. Unknown keys are never returned as an error; the returned error is only for a missing, unreadable, or unparseable file.
func (UnknownKey) FullKey ¶ added in v0.66.0
func (u UnknownKey) FullKey() string
FullKey renders the key with its table prefix, e.g. "sandbox.read_dir".