config

package
v0.69.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const (
	LaunchMaxConcurrentDefault  = 3
	LaunchStartupTimeoutDefault = 3 * time.Minute
	LaunchSettleTimeoutDefault  = 10 * time.Second
)

Launch tuning defaults. MaxConcurrent defaults to 3 because the #1092 evidence showed ~4 concurrent startups completing fine while the 5th stalled.

View Source
const (
	TodoEmitScenario = "scenario" // emit only for scenario-scoped lists (default)
	TodoEmitAll      = "all"      // emit for every scope
	TodoEmitOff      = "off"      // never emit
)

Emit-events modes for the task-list subsystem.

View Source
const (
	NotifyPriorityLow    = "low"
	NotifyPriorityNormal = "normal"
	NotifyPriorityHigh   = "high"
)

Notification priority levels for `gr notify`.

View Source
const (
	TrackerStateOpen   = "open"
	TrackerStateClosed = "closed"
	TrackerStateAll    = "all"
)

Tracker active-state values for TrackerConfig.ActiveState.

View Source
const (
	TrackerReapStop   = "stop"   // stop the agent (recoverable via gr resume)
	TrackerReapDelete = "delete" // soft-delete the session (recoverable via gr restore)
	TrackerReapNone   = "none"   // leave the session; report only
)

Tracker reap-policy values for TrackerConfig.Reap.

View Source
const (
	ActionCommand  = "command"
	ActionSession  = "session"
	ActionScenario = "scenario"
	ActionMessage  = "message"
	ActionTracker  = "tracker"
)

Action type values for ActionConfig.Type.

View Source
const (
	OverlapSkip  = "skip"
	OverlapAllow = "allow"
	OverlapQueue = "queue" // deferred to v2
)

Overlap policy values for TriggerPolicy.Overlap.

View Source
const (
	CleanupAlways    = "always"     // delete on any stop
	CleanupOnSuccess = "on_success" // delete only on a clean (exit 0) stop
)

Auto-cleanup mode values for a session action's AutoCleanup.

View Source
const DefaultDeleteRetention = 24 * time.Hour

DefaultDeleteRetention is the soft-delete retention window used when [delete] retention is unset.

View Source
const DefaultNotifyMaxPerHour = 12

DefaultNotifyMaxPerHour is the rolling-hour cap on low/normal push notifications used when [notifications] max_per_hour is unset.

View Source
const DefaultTodoClaimLease = 30 * time.Minute

DefaultTodoClaimLease is the default claim-lease window: an in-progress item whose owner has made no progress for this long is auto-reopened. 0 disables.

View Source
const RedactedMask = "***"

RedactedMask is the placeholder substituted for secret-bearing values when a config is rendered for a caller that must not see raw secrets.

View Source
const ReservedTriggerNamePrefix = "scenario:"

ReservedTriggerNamePrefix is reserved for the daemon's namespaced scenario-embedded trigger names (scenario:<id>:<name>). A config-origin trigger name must not use it, or it would be misrouted to a scenario lookup.

View Source
const (
	TrackerProviderGitHub = "github"
)

Tracker provider values for TrackerConfig.Provider.

Variables

View Source
var DefaultTrustedAssociations = []string{"OWNER", "MEMBER", "COLLABORATOR"}

DefaultTrustedAssociations is the trusted author_association set used when pr_watch.trusted_author_associations is unset. It is the "has write access to, or is a member of the org that owns, the repo" tier; CONTRIBUTOR is excluded deliberately (on a public repo it means only "merged a commit once", and bots can carry it — see the author-trust design doc).

View Source
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 DiffFromDefaults added in v0.69.0

func DiffFromDefaults(cfg *Config, toLabel string) (string, error)

DiffFromDefaults returns a unified diff (built-in defaults → cfg) of the two TOML renderings. toLabel names the "to" side in the diff header (e.g. the config file path, or "effective"). An empty return means cfg is byte-for-byte identical to the built-in defaults.

func EffectiveTOML added in v0.69.0

func EffectiveTOML(cfg *Config) ([]byte, error)

EffectiveTOML renders cfg as TOML — the effective, fully-merged configuration (built-in defaults overlaid with the user's file). This is what `gr config show` prints and what the GUI's config viewer displays.

func Expand

func Expand(s string, vars TemplateVars) (string, error)

func ExpandPath added in v0.11.0

func ExpandPath(p string) string

func ExpandPathRelative added in v0.66.0

func ExpandPathRelative(p, baseDir string) string

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 ExpandTrigger added in v0.69.0

func ExpandTrigger(s string, vars TriggerVars) (string, error)

ExpandTrigger replaces {token} occurrences in s using the trigger variable set. An unknown token is an error (parity with Expand's discipline).

func IncludeEnvVarName added in v0.19.0

func IncludeEnvVarName(repoBasename string) string

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 NormalizeNotifyPriority added in v0.69.0

func NormalizeNotifyPriority(p string) (string, bool)

NormalizeNotifyPriority resolves a user-supplied priority to a canonical level, defaulting an empty value to "normal". It reports ok=false for an unrecognised value so callers can reject it.

func ParseDurationWithDays added in v0.3.0

func ParseDurationWithDays(s string) (time.Duration, error)

func ResolveConfigPath added in v0.66.0

func ResolveConfigPath(explicit string) (path string, exists bool, err error)

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 ResolvePath(p string) string

func ResolveProfile added in v0.18.0

func ResolveProfile() (profile string, appName string, err error)

func ValidateIncludes added in v0.69.0

func ValidateIncludes(mainRepoPath string, includes []string) error

ValidateIncludes checks a set of include paths against the main repo for the collisions that would break the worktree/env-var layout: an include equal to the main repo, duplicate basenames (across the main repo and the includes), and generated GRAITH_INCLUDE_* env-var name collisions. Included worktrees and their env vars are keyed by basename, so these must be unique. Used both by repo-config validation and by the session-create path for scenario-supplied includes (issue #1046), so both surfaces reject the same footguns up front rather than failing with a low-level git error mid-setup.

func ValidateTriggerStructure added in v0.69.0

func ValidateTriggerStructure(where string, t *TriggerConfig) []error

ValidateTriggerStructure runs the config-independent structural validation for a single trigger: exactly one source, the source's own rules, the action's shape, and the policy. Config-dependent checks (allowed_repo_paths and [orchestrator] enabled) are layered on separately by validateActionConfigDeps. It is exported so the scenario-file loader can hold scenario-embedded [[trigger]] blocks to the same shape rules without a full *Config.

Types

type ActionConfig added in v0.69.0

type ActionConfig struct {
	Type string `toml:"type"` // command | session | scenario | message | tracker

	// command:
	Command  string `toml:"command"`
	Repo     string `toml:"repo"`     // required for schedule commands; rejected for watch
	Timeout  string `toml:"timeout"`  // max run time; default 5m
	Mutating bool   `toml:"mutating"` // may write its execution root; rejected in v1
	Sandbox  *bool  `toml:"sandbox"`  // nil => default true; false runs unconfined
	// SandboxConfig is extra sandbox grants merged onto the base command profile,
	// mirroring the MCP-server pattern (MCPServerConfig.SandboxConfig).
	SandboxConfig *SandboxConfig `toml:"sandbox_config"`

	// session:
	Prompt string `toml:"prompt"`
	Agent  string `toml:"agent"`
	Model  string `toml:"model"`
	Ensure bool   `toml:"ensure"` // idempotent ensure-reviewer (watch source only)
	// AutoCleanup soft-deletes a trigger-spawned session once it stops, so a
	// finished briefing/report session doesn't clutter `gr list`. It is a union
	// of bool and string: absent/false/"" disables it; true (or "always")
	// deletes on any stop; "on_success" deletes only on a clean (exit 0) stop.
	// Decoded as any so TOML can supply either a bool or the string enum; use
	// AutoCleanupMode to normalise. Session action only.
	AutoCleanup any `toml:"auto_cleanup"`
	// IdleTimeout auto-stops the spawned session after it sits idle (agent at
	// rest, no attached client) this long, overriding the agent default. A Go
	// duration ("1m", "5m"). Session action only. When unset, an
	// auto_cleanup="always" session defaults to a short idle window so a finished
	// briefing reaps itself promptly (finish -> idle-stop -> soft-delete); see
	// SessionIdleTimeout.
	IdleTimeout string `toml:"idle_timeout"`

	// scenario:
	Scenario string `toml:"scenario"`

	// tracker: keep live sessions in sync with an issue tracker. On each
	// scheduled fire the daemon polls the tracker for active issues and
	// reconciles sessions against them — spawning one per active issue (seeded
	// with the templated Prompt above) and reaping the session when its issue
	// leaves the active state. Schedule source only. See TrackerConfig and
	// docs/design/2026-07-16-tracker-poll-action.md.
	Tracker *TrackerConfig `toml:"tracker"`

	// message:
	Body string `toml:"body"`

	// notify (any action type): when NotifyOnComplete is set, the daemon fires a
	// proactive push notification (see [notifications]) once the action finishes
	// firing. NotifyMessage is the body (templated with the trigger vars;
	// defaults to a generic "<name> completed"); NotifyPriority is low/normal/high
	// (defaults to normal, or high when the action errored).
	NotifyOnComplete bool   `toml:"notify_on_complete"`
	NotifyMessage    string `toml:"notify_message"`
	NotifyPriority   string `toml:"notify_priority"`

	Deliver DeliverConfig `toml:"deliver"`
}

ActionConfig is the shared action vocabulary. Type selects the verb.

func (ActionConfig) AutoCleanupMode added in v0.69.0

func (a ActionConfig) AutoCleanupMode() (string, error)

AutoCleanupMode normalises the auto_cleanup union to "" (disabled), CleanupAlways, or CleanupOnSuccess. true is shorthand for "always"; false and an absent value are disabled. Any other value is a config error.

func (ActionConfig) RepoPath added in v0.69.0

func (a ActionConfig) RepoPath() string

RepoPath returns the action's configured repo canonicalised the same way sessions and the store CLI treat a repo path: a leading ~/ expanded, made absolute, and symlinks resolved (via ResolvePath). This matters for repo-store delivery, whose namespace is keyed off the repo path — a raw ~/... or a symlinked spelling would otherwise scope to a different store than the one agents read. It returns "" when no repo is set — unlike ResolvePath/ExpandPath, which would resolve "" to the working directory — so callers can still distinguish "unset" (shared store / no execution root) from a resolved path.

func (ActionConfig) Sandboxed added in v0.69.0

func (a ActionConfig) Sandboxed() bool

Sandboxed reports whether a command action runs sandboxed (nil => true).

func (ActionConfig) SessionIdleTimeout added in v0.69.0

func (a ActionConfig) SessionIdleTimeout() (time.Duration, error)

SessionIdleTimeout resolves the idle-stop window for a spawned session action. An explicit idle_timeout always wins. Otherwise an auto_cleanup="always" session gets defaultAutoCleanupIdle so it reaps itself promptly. "on_success" is deliberately not auto-idled: an idle-stop is a non-zero (SIGTERM) exit that "on_success" would not clean up, so idling it would just leave stopped clutter — the very thing auto_cleanup avoids. 0 means "use the agent default".

func (ActionConfig) TimeoutDuration added in v0.69.0

func (a ActionConfig) TimeoutDuration() time.Duration

TimeoutDuration returns a command action's timeout, defaulting to 5m.

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"`
	// HeadlessCapable marks an agent as supporting headless stream-json mode
	// (issue #1075). Unset means not capable — only agents explicitly flagged
	// (Claude Code in v1) may run headless, so a --headless request against an
	// unsupported agent fails closed rather than silently downgrading.
	HeadlessCapable *bool `json:"headless_capable,omitempty" toml:"headless_capable"`
}

func (Agent) HeadlessCapableEnabled added in v0.69.0

func (a Agent) HeadlessCapableEnabled() bool

HeadlessCapableEnabled reports whether this agent may run in headless stream-json mode. Defaults to false when unset.

func (Agent) IdleTimeoutDuration

func (a Agent) IdleTimeoutDuration() time.Duration

func (Agent) InterruptCountValue added in v0.66.2

func (a Agent) InterruptCountValue() int

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

func (a Agent) InterruptDelay() time.Duration

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 (a Agent) PreTrustWorkspaceEnabled() bool

func (Agent) PromptInjectionEnabled added in v0.32.0

func (a Agent) PromptInjectionEnabled() bool

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

func (a Approvals) HookEnabled() bool

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

func (a Approvals) ResolveBackend() (backend, deprecation string, err error)

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:

  1. 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).
  2. 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.
  3. Else, the "prompt" backend (no automation).

func (Approvals) TimeoutDuration added in v0.13.0

func (a Approvals) TimeoutDuration() time.Duration

func (Approvals) Validate added in v0.64.5

func (a Approvals) Validate() error

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 CodexOptions added in v0.69.0

type CodexOptions struct {
	Profile         string `json:"profile,omitempty"`
	ReasoningEffort string `json:"reasoning_effort,omitempty"`
	ServiceTier     string `json:"service_tier,omitempty"`
	WebSearch       bool   `json:"web_search,omitempty"`
	ApprovalPolicy  string `json:"approval_policy,omitempty"`
}

CodexOptions holds typed per-session options for the Codex CLI (issue #1186). Each maps to a Codex flag or `-c` config override and is emitted only when set, so an unset field leaves Codex's own default untouched. The session model is tracked separately (SessionState.Model / CreateOpts.Model) and is not repeated here. These are Codex-specific: setting any against a non-codex agent is an error rather than a silent no-op. Reasoning effort and service tier are passed as `-c model_reasoning_effort=…` / `-c service_tier=…` because Codex has no dedicated flag for them; profile, web search, and approval policy have flags.

func (CodexOptions) IsZero added in v0.69.0

func (o CodexOptions) IsZero() bool

IsZero reports whether no Codex option is set.

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"`
	Delete           Delete             `toml:"delete"`
	Todo             TodoConfig         `toml:"todo"`
	Sandbox          SandboxConfig      `toml:"sandbox"`
	Approvals        Approvals          `toml:"approvals"`
	Status           StatusConfig       `toml:"status"`
	GitPull          GitPullConfig      `toml:"git_pull"`
	Launch           LaunchConfig       `toml:"launch"`
	PRWatch          PRWatchConfig      `toml:"pr_watch"`
	MCPServers       []MCPServerConfig  `toml:"mcp_servers"`
	Overlay          Overlay            `toml:"overlay"`
	Orchestrator     OrchestratorConfig `toml:"orchestrator"`
	Remote           RemoteConfig       `toml:"remote"`
	Input            InputConfig        `toml:"input"`
	Agents           map[string]Agent   `toml:"agents"`
	Triggers         []TriggerConfig    `toml:"trigger"`  // [[trigger]] array
	TriggersRuntime  TriggersRuntime    `toml:"triggers"` // [triggers] table (daemon-wide settings)
	Headless         HeadlessConfig     `toml:"headless"` // [headless] table (issue #1075)
}

func Default

func Default() *Config

func Load

func Load(path string) (*Config, error)

func LoadOrDefault

func LoadOrDefault(path string) (*Config, error)

func RedactSecrets added in v0.69.0

func RedactSecrets(cfg *Config) *Config

RedactSecrets returns a copy of cfg with secret-bearing values masked: the per-server and per-agent `env` maps, whose values routinely hold tokens and API keys inline in config.toml. Map keys are preserved (so the shape stays visible); only the values are replaced with RedactedMask. cfg is not mutated.

The daemon renders this — not the raw config — over the control protocol, so a remote paired human, or a local session reading via the socket, sees the configuration structure without its secrets. `gr config show`/`diff` read the file directly (not through the daemon) and are deliberately unaffected.

func (*Config) AvailableRepoPaths added in v0.66.13

func (c *Config) AvailableRepoPaths() []string

AvailableRepoPaths returns the repo paths the orchestrator may use, combining the allowed_repo_paths list and the [[repos]] entries with ~ expanded, in config order and de-duplicated. It returns nil when none are configured.

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

func (c *Config) RepoPathAllowed(repoPath string) bool

func (*Config) Validate added in v0.19.0

func (c *Config) Validate() error

type Delete added in v0.66.16

type Delete struct {
	Retention string `toml:"retention"`
}

Delete configures the soft-delete behaviour of `gr delete`. When retention is a positive duration, `gr delete` marks a session deleted and keeps its worktree/state for the window; the daemon purges it after the window elapses. A retention of "0" disables soft delete: `gr delete` is then rejected (with a message pointing at `gr purge`), since delete must never destroy — `gr purge` remains the way to hard-delete immediately.

func (Delete) RetentionDuration added in v0.66.16

func (d Delete) RetentionDuration() time.Duration

RetentionDuration resolves the configured soft-delete retention window. An unset value defaults to DefaultDeleteRetention (24h); "0" (or any zero duration) disables soft delete. An unparseable value falls back to the default so a typo never silently turns off recovery.

type DeliverConfig added in v0.69.0

type DeliverConfig struct {
	Inbox string `toml:"inbox"` // session name, "orchestrator", or a template like "{session_name}"
	Topic string `toml:"topic"` // pub/sub topic
	Store string `toml:"store"` // store key (prefix "shared:" for the shared store)
	Wake  bool   `toml:"wake"`  // resume a non-orchestrator stopped inbox target
}

DeliverConfig routes action output. All fields are templated at fire time.

type GitPullConfig added in v0.42.0

type GitPullConfig struct {
	Enabled  bool   `toml:"enabled"`
	Interval string `toml:"interval"`
}

func (GitPullConfig) IntervalDuration added in v0.42.0

func (g GitPullConfig) IntervalDuration() time.Duration

type HeadlessConfig added in v0.69.0

type HeadlessConfig struct {
	Experimental bool `toml:"experimental"`
	Default      bool `toml:"default"`
}

HeadlessConfig is the [headless] block gating headless stream-json sessions (issue #1075). Headless is inert unless Experimental is true — the control protocol it uses is an SDK-internal contract, so v1 is opt-in and experimental. Default, when Experimental is on, decides whether new sessions go headless without an explicit --headless.

type InputConfig added in v0.66.16

type InputConfig struct {
	// DragArrowKeys enables touch/hold-and-drag arrow keys:
	// press-and-hold the left mouse button then drag to emit discrete arrow-key
	// presses to the focused pane. Off by default because it repurposes
	// left-drag (which terminals otherwise use for text selection). Mouse-wheel
	// scrolling is always passed through unchanged.
	DragArrowKeys bool `toml:"drag_arrow_keys"`
	// DragArrowThreshold is the number of cells of drag movement that produces
	// one arrow-key press. Values below 1 fall back to the default.
	DragArrowThreshold int `toml:"drag_arrow_threshold"`
}

InputConfig is the optional [input] block controlling terminal input gestures in the attach passthrough loop.

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 LaunchConfig added in v0.69.0

type LaunchConfig struct {
	// MaxConcurrent bounds how many agent spawns may be in their startup window
	// at once. Values < 1 fall back to the default (LaunchMaxConcurrentDefault).
	MaxConcurrent int `toml:"max_concurrent"`
	// StartupTimeout is how long a session may stay running with no output before
	// the startup watchdog kills and restarts it fresh. "0" disables the
	// watchdog; empty uses the default (LaunchStartupTimeoutDefault).
	StartupTimeout string `toml:"startup_timeout"`
	// SettleTimeout caps how long a launch holds its throttle slot waiting for
	// the session's first output before releasing it anyway. Empty uses the
	// default (LaunchSettleTimeoutDefault); "0" releases immediately after spawn.
	SettleTimeout string `toml:"settle_timeout"`
}

LaunchConfig bounds concurrent agent-session startup and recovers sessions that stall during launch (issue #1092). Bursts of `gr new` otherwise let many heavyweight agent runtimes initialise at once, and the tail can stall for minutes or hang forever at ~9MB RSS (sandbox wrapper only, agent never loaded).

func (LaunchConfig) MaxConcurrentOrDefault added in v0.69.0

func (l LaunchConfig) MaxConcurrentOrDefault() int

MaxConcurrentOrDefault returns the configured concurrency, clamped to a sensible minimum. A non-positive value means "use the default".

func (LaunchConfig) SettleTimeoutDuration added in v0.69.0

func (l LaunchConfig) SettleTimeoutDuration() time.Duration

SettleTimeoutDuration returns how long a slot waits for first output. Empty uses the default; an explicit "0" releases the slot as soon as the spawn returns.

func (LaunchConfig) StartupTimeoutDuration added in v0.69.0

func (l LaunchConfig) StartupTimeoutDuration() time.Duration

StartupTimeoutDuration returns the watchdog threshold. Empty uses the default; an explicit "0" (or any non-positive parse) disables the watchdog.

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

type Messages struct {
	MaxAge       string `toml:"max_age"`
	MaxPerStream int    `toml:"max_per_stream"`
}

func (Messages) MaxAgeDuration added in v0.3.0

func (m Messages) MaxAgeDuration() time.Duration

type Notifications added in v0.2.0

type Notifications struct {
	Enabled    bool   `toml:"enabled"`
	OnApproval bool   `toml:"on_approval"`
	OnStopped  bool   `toml:"on_stopped"`
	Command    string `toml:"command"`
	// Backend selects how proactive `gr notify` push notifications are delivered:
	// "macos" (osascript desktop notification; the default when unset) or
	// "command" (run [notifications] command with GRAITH_NOTIFY_* env vars). Other
	// backends (ntfy/pushover/slack) are planned follow-ups and rejected for now.
	Backend string `toml:"backend"`
	// MaxPerHour rate-limits low/normal push notifications over a rolling hour so a
	// misbehaving trigger can't storm the user. <=0 uses DefaultNotifyMaxPerHour.
	// High-priority notifications bypass this limit.
	MaxPerHour int `toml:"max_per_hour"`
	// QuietHoursStart / QuietHoursEnd define a daily window ("HH:MM", 24-hour) in
	// which low/normal push notifications are suppressed. The window may wrap past
	// midnight (start > end, e.g. 22:00-07:00). Both must be set to take effect.
	// High-priority notifications bypass quiet hours.
	QuietHoursStart string `toml:"quiet_hours_start"`
	QuietHoursEnd   string `toml:"quiet_hours_end"`
}

func (Notifications) InQuietHours added in v0.69.0

func (n Notifications) InQuietHours(t time.Time) bool

InQuietHours reports whether the local time t falls within the configured quiet-hours window. It supports a window that wraps past midnight (start > end). An unset or unparseable window returns false (fail-open: a typo mutes nothing rather than everything — Validate rejects a malformed window at load).

func (Notifications) MaxPerHourValue added in v0.69.0

func (n Notifications) MaxPerHourValue() int

MaxPerHourValue returns the effective rolling-hour push-notification cap, defaulting to DefaultNotifyMaxPerHour when unset (<=0).

func (Notifications) NotifyBackendName added in v0.69.0

func (n Notifications) NotifyBackendName() string

NotifyBackendName returns the effective push-notification backend, defaulting to "macos" when unset.

func (Notifications) QuietHoursConfigured added in v0.69.0

func (n Notifications) QuietHoursConfigured() bool

QuietHoursConfigured reports whether a quiet-hours window is fully set.

func (Notifications) Validate added in v0.69.0

func (n Notifications) Validate() error

Validate checks the [notifications] block for static errors: an unknown push backend, a malformed quiet-hours window, or a "command" backend with no command set. It fails closed so a typo surfaces at config-load rather than as a silent no-op notification.

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(defaultAgent string) string

AgentName resolves the agent type the orchestrator session runs as. An explicit [orchestrator] agent wins; otherwise it inherits the top-level default_agent (passed in by the caller, which has access to the full config), falling back to "claude" only when neither is set.

func (OrchestratorConfig) IdleTimeoutDuration added in v0.42.0

func (o OrchestratorConfig) IdleTimeoutDuration() time.Duration

type OrchestratorSandboxConfig added in v0.46.0

type OrchestratorSandboxConfig struct {
	ReadDirs   []string `toml:"read_dirs"`
	WriteDirs  []string `toml:"write_dirs"`
	ReadFiles  []string `toml:"read_files"`
	WriteFiles []string `toml:"write_files"`
}

type Overlay added in v0.56.0

type Overlay struct {
	ShortcutKeys string `toml:"shortcut_keys"`
}

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"`
	NotifyPRComments      bool   `toml:"notify_pr_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"`
	// CommentAuthorAllowlist trusts individual comment authors by login,
	// case-insensitively and matched against the full "<name>[bot]" string. It is
	// the ONLY way to trust a bot or GitHub App (their author_association is
	// unreliable — a bot can carry NONE or CONTRIBUTOR), and also covers named
	// humans. Defaults empty; discovery is via the orchestrator trust prompt.
	CommentAuthorAllowlist []string `toml:"comment_author_allowlist"`
	// TrustedAuthorAssociations is the set of GitHub author_association values
	// treated as trusted. Defaults to OWNER/MEMBER/COLLABORATOR when unset (the
	// "has write access to, or is a member of the org that owns, the repo" tier);
	// CONTRIBUTOR is deliberately excluded. Values are normalised to upper-case
	// via TrustedAssociationSet.
	TrustedAuthorAssociations []string `toml:"trusted_author_associations"`
	// NotifyUntrustedAuthors, when true, sends a one-time metadata-only message to
	// the orchestrator the first time a comment from a not-yet-trusted author is
	// seen, so the human can decide whether to allowlist them. It NEVER carries
	// the untrusted comment body. False disables the prompt entirely (silent drop,
	// still logged).
	NotifyUntrustedAuthors bool `toml:"notify_untrusted_authors"`
}

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.

Every notify_* sub-option defaults on: enabling pr_watch is meant to be a single switch (enabled = true) that turns on all notifications, and users selectively disable the classes they don't want. The classes are still gated separately because they carry different authority — a CI failure is a machine verdict (safe to act on), while a review comment or decision is human intent that may not be actionable — so each can be turned off independently.

Comments come in two distinct kinds, each with its own gate:

  • NotifyReviewComments covers inline code-review comments (the pulls/{n}/comments surface) — feedback anchored to a file and line.
  • NotifyPRComments covers regular conversation comments on the PR thread (the issues/{n}/comments surface) — issue-style comments not tied to a line of code.

They are separate signals: a reviewer leaving inline nits and someone dropping a "ship it" on the conversation thread differ, and a user may want one without the other.

For backward compatibility, notify_pr_comments used to be folded into notify_review_comments; see applyPRWatchCommentCompat, which keeps an older config that only set notify_review_comments delivering conversation comments.

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).

func (PRWatchConfig) TrustedAssociationSet added in v0.69.0

func (p PRWatchConfig) TrustedAssociationSet() map[string]bool

TrustedAssociationSet returns the resolved set of trusted author_association values as an upper-cased lookup set. A configured list is normalised to upper-case (GitHub returns the enum upper-cased, but config is hand-written) and empty/whitespace entries are dropped.

The nil vs present-but-empty distinction is load-bearing and fails CLOSED (issue #1039):

  • A NIL slice means "unset" (the Go zero value, or a config built without defaults) and falls back to DefaultTrustedAssociations. Load() seeds the field from default_config.toml, so an unset key in a real config resolves to the default three; nil here covers direct struct construction.
  • A PRESENT-but-empty slice (trusted_author_associations = []) is an explicit "trust no association" — allowlist-only mode — and is honoured as an empty set. go-toml/v2 decodes `= []` to a non-nil empty slice, so it is distinguishable from an absent key, and we must NOT silently widen it back to the default (that would fail open on an operator asking to lock the gate down).

type PairRate added in v0.66.3

type PairRate struct {
	Count int
	Per   time.Duration
}

PairRate is a parsed pair_request_rate: Count events per Per duration.

func ParsePairRequestRate added in v0.66.3

func ParsePairRequestRate(s string) (PairRate, error)

ParsePairRequestRate parses a "<n>/<unit>" rate such as "5/min". The unit is one of sec/min/hour (with the aliases second/minute/hour). The count must be a positive integer. Any other shape is a hard error (fail-closed).

type Paths

type Paths struct {
	Profile        string
	AppName        string
	ConfigFile     string
	DataDir        string
	RuntimeDir     string
	SocketPath     string
	PIDFile        string
	StateFile      string
	HumanTokenFile string
	LogDir         string
	DaemonLog      string
	MessagesDB     string
	TodosDB        string
	TmpDir         string
}

func ResolvePaths

func ResolvePaths() (Paths, error)

func (Paths) EnsureDirs

func (p Paths) EnsureDirs() error

func (Paths) WithDataDir added in v0.21.0

func (p Paths) WithDataDir(dataDir string) Paths

type RemoteConfig added in v0.66.3

type RemoteConfig struct {
	// Enabled turns the remote listener on. Off by default; when false the rest
	// of the block is not validated so a disabled block never blocks startup.
	Enabled bool `toml:"enabled"`
	// Mode selects the transport: "tsnet" (embedded Tailscale via tsnet) or
	// "interface" (bind the host's existing tailnet interface IP).
	Mode string `toml:"mode"`
	// Hostname is the tsnet node name / MagicDNS label (tsnet mode).
	Hostname string `toml:"hostname"`
	// Port is the TCP port the listener binds.
	Port int `toml:"port"`
	// AuthKeyFile is the path to a tsnet auth key (tsnet mode only).
	AuthKeyFile string `toml:"auth_key_file"`
	// Tags are the tsnet ACL tags applied to the node (tsnet mode only).
	Tags []string `toml:"tags"`
	// AllowTailnetUsers is the WhoIs allowlist (Gate 1). Entries are either a
	// tailnet user email or a "tag:"-prefixed tag. A bare "tag:" entry opts
	// tagged nodes in; with no tag entry, tagged nodes are disallowed.
	AllowTailnetUsers []string `toml:"allow_tailnet_users"`
	// RequirePairing requires per-device pairing (Gate 2) for human-level
	// rights. Defaults to true; false is UNSAFE (trusts the tailnet identity
	// alone) and is restricted to read-only access — see the design doc §B.2.
	RequirePairing bool `toml:"require_pairing"`
	// PairRequestRate is the anti-flood limit on pending pair requests, written
	// "<n>/<unit>" (e.g. "5/min"); units are sec, min, or hour. Empty means no
	// configured limit here (the daemon applies its own default).
	PairRequestRate string `toml:"pair_request_rate"`
}

RemoteConfig is the optional, off-by-default [remote] block that exposes a tailnet-facing control listener (see the native-app design doc §A.4/§B). It is fail-closed: when Enabled, an invalid block is a hard config-load error (static validation only — runtime listener provisioning failures, e.g. a missing tailnet IP or cert, are handled by the remote listener, not here).

func (RemoteConfig) AllowsTaggedNodes added in v0.66.3

func (r RemoteConfig) AllowsTaggedNodes() bool

AllowsTaggedNodes reports whether any allow_tailnet_users entry opts tagged nodes in (a "tag:"-prefixed entry). With no such entry, tagged nodes — which WhoIs resolves with no user — are disallowed by default.

func (RemoteConfig) Validate added in v0.66.3

func (r RemoteConfig) Validate() error

Validate checks the [remote] block for static contradictions. Rules are only enforced when Enabled — a disabled block (even with otherwise-invalid values) always loads. It is fail-closed: an invalid enabled block is a hard error.

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 ScheduleConfig added in v0.69.0

type ScheduleConfig struct {
	Cron     string `toml:"cron"`     // 5-field cron, or @hourly/@daily/@weekly/@monthly
	Every    string `toml:"every"`    // Go duration (supports "7d"): "15m", "1h30m"
	Timezone string `toml:"timezone"` // IANA zone for cron; default = daemon local time
}

ScheduleConfig is the time-driven source. Exactly one of Cron/Every is set.

type StatusBar added in v0.3.0

type StatusBar struct {
	Enabled  bool   `toml:"enabled"`
	Position string `toml:"position"`
}

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 TemplateVars struct {
	Username                 string
	AgentSessionID           string
	SessionName              string
	SessionID                string
	WorktreePath             string
	ForkSourceAgentSessionID string
	Model                    string
}

type TodoConfig added in v0.69.0

type TodoConfig struct {
	EmitEvents string `toml:"emit_events"` // "scenario" (default) | "all" | "off"
	ClaimLease string `toml:"claim_lease"` // Go duration; "" = 30m default; "0" disables
	Retention  string `toml:"retention"`   // Go duration; "" or "0" = keep done items forever
}

TodoConfig is the [todo] block. It governs the first-class todo subsystem (issue #591): event emission on state change, the claim lease that reclaims stranded in-progress items, and the retention window that sweeps done items.

func (TodoConfig) ClaimLeaseDuration added in v0.69.0

func (t TodoConfig) ClaimLeaseDuration() time.Duration

ClaimLeaseDuration resolves the claim-lease window. Unset (or, as a fail-safe, unparseable — though Validate rejects that at startup) defaults to 30m; an explicit "0" disables the lease sweep.

func (TodoConfig) EmitMode added in v0.69.0

func (t TodoConfig) EmitMode() string

EmitMode resolves the emit-events mode, defaulting to "scenario".

func (TodoConfig) RetentionDuration added in v0.69.0

func (t TodoConfig) RetentionDuration() time.Duration

RetentionDuration resolves the done-item retention window. Unset or zero keeps done items indefinitely (returns 0).

type TrackerConfig added in v0.69.0

type TrackerConfig struct {
	Provider      string   `toml:"provider"`       // "github" (v1); "" defaults to github
	Repo          string   `toml:"repo"`           // resolves the tracker + is the spawn repo (required)
	ActiveState   string   `toml:"active_state"`   // open | closed | all (default open)
	ActiveLabels  []string `toml:"active_labels"`  // active iff the issue has one of these (empty = any state-matching issue)
	Assignee      string   `toml:"assignee"`       // optional tracker assignee filter (e.g. "@me")
	Grace         string   `toml:"grace"`          // inactive this long before reaping; default 5m
	MaxConcurrent int      `toml:"max_concurrent"` // cap on live tracker sessions (0 = unlimited)
	Reap          string   `toml:"reap"`           // stop | delete | none (default stop)
	Limit         int      `toml:"limit"`          // max issues fetched per poll (default 50)
}

TrackerConfig configures a tracker action's poll + reconcile behaviour. The spawned sessions' agent/model/prompt come from the enclosing ActionConfig; this block is the tracker-specific part. See docs/design/2026-07-16-tracker-poll-action.md.

func (TrackerConfig) ActiveStateOr added in v0.69.0

func (t TrackerConfig) ActiveStateOr() string

ActiveStateOr returns the configured active state, defaulting to open.

func (TrackerConfig) GraceDuration added in v0.69.0

func (t TrackerConfig) GraceDuration() time.Duration

GraceDuration returns the reap grace window, defaulting to 5m.

func (TrackerConfig) LimitOr added in v0.69.0

func (t TrackerConfig) LimitOr() int

LimitOr returns the per-poll issue fetch cap, defaulting to 50.

func (TrackerConfig) ProviderOr added in v0.69.0

func (t TrackerConfig) ProviderOr() string

ProviderOr returns the configured provider, defaulting to github.

func (TrackerConfig) ReapMode added in v0.69.0

func (t TrackerConfig) ReapMode() string

ReapMode returns the configured reap policy, defaulting to stop.

func (TrackerConfig) RepoPath added in v0.69.0

func (t TrackerConfig) RepoPath() string

RepoPath returns the tracker repo canonicalised the same way ActionConfig.RepoPath treats a repo (see that method). Empty when unset.

type TriggerConfig added in v0.69.0

type TriggerConfig struct {
	Name     string          `toml:"name"`
	Enabled  *bool           `toml:"enabled"`  // nil => default true; explicit false disables
	Schedule *ScheduleConfig `toml:"schedule"` // time-driven source
	Watch    *WatchConfig    `toml:"watch"`    // file-event source
	Action   ActionConfig    `toml:"action"`
	Policy   TriggerPolicy   `toml:"policy"`
}

TriggerConfig is one [[trigger]] block. A trigger is (source) -> (action): exactly one of Schedule (#592) or Watch (#593) is the source, and Action is what runs. Everything below the source line is shared between the two source kinds. See docs/design/2026-07-11-triggers-design.md.

func (TriggerConfig) IsSchedule added in v0.69.0

func (t TriggerConfig) IsSchedule() bool

IsSchedule / IsWatch report the source kind.

func (TriggerConfig) IsWatch added in v0.69.0

func (t TriggerConfig) IsWatch() bool

func (TriggerConfig) TriggerEnabled added in v0.69.0

func (t TriggerConfig) TriggerEnabled() bool

TriggerEnabled reports whether the trigger is enabled (nil => true).

type TriggerPolicy added in v0.69.0

type TriggerPolicy struct {
	CatchUp   bool   `toml:"catch_up"`   // default false: never backfill missed fires
	Overlap   string `toml:"overlap"`    // "" or "skip" (default) | "allow" | "queue"(v2)
	RateLimit string `toml:"rate_limit"` // "N/duration"; default "5/30m"
}

TriggerPolicy controls missed-run / overlap / rate-limit behaviour.

func (TriggerPolicy) OverlapMode added in v0.69.0

func (p TriggerPolicy) OverlapMode() string

OverlapMode returns the effective overlap policy (empty => skip).

func (TriggerPolicy) RateLimitParsed added in v0.69.0

func (p TriggerPolicy) RateLimitParsed() (int, time.Duration)

RateLimitParsed parses "N/duration" (e.g. "5/30m"), defaulting to 5 per 30m.

type TriggerVars added in v0.69.0

type TriggerVars struct {
	Name         string // trigger name
	Date         string // e.g. 2026-07-11
	Datetime     string // RFC3339
	FireTime     string // scheduled/observed fire instant (RFC3339)
	SessionName  string // watch source: the bound session
	WorktreePath string // watch source: the bound session's worktree
	ChangedFiles string // watch source: comma-separated changed paths (or "")
	ChangeCount  string // watch source: number of changed paths
	// Tracker action: the issue a spawned session is seeded from. These are known
	// template tokens (they live in this shared struct), so they expand to the
	// empty string — not an error — outside a tracker prompt; a genuinely unknown
	// token still errors.
	IssueNumber string // e.g. "643"
	IssueTitle  string
	IssueBody   string
	IssueURL    string
	IssueLabels string // comma-separated label names (or "")
}

TriggerVars is the variable set available to trigger delivery/message templates. It is deliberately separate from TemplateVars (which is a fixed struct for agent-arg expansion) — trigger templates have their own tokens and must not silently accept agent-arg names. Like Expand, ExpandTrigger errors on an unknown {token}.

type TriggersRuntime added in v0.69.0

type TriggersRuntime struct {
	MaxConcurrent int `toml:"max_concurrent"` // default 4
}

TriggersRuntime holds daemon-wide trigger settings ([triggers] table, distinct from the [[trigger]] array).

func (TriggersRuntime) MaxConcurrentOr added in v0.69.0

func (r TriggersRuntime) MaxConcurrentOr() int

MaxConcurrentOr returns the daemon-wide concurrency cap, defaulting to 4.

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".

type WatchConfig added in v0.69.0

type WatchConfig struct {
	Repo     string   `toml:"repo"`     // bind to sessions on this repo
	Role     string   `toml:"role"`     // bind to sessions with this scenario role
	Paths    []string `toml:"paths"`    // optional include globs (worktree-relative)
	Ignore   []string `toml:"ignore"`   // extra ignore globs (added to built-ins + .gitignore)
	Debounce string   `toml:"debounce"` // quiet-window; default 30s
}

WatchConfig is the file-event source. It is a POLICY selector (repo/role), never a literal live session name in config. Binds to matching sessions as they are created.

func (WatchConfig) DebounceDuration added in v0.69.0

func (w WatchConfig) DebounceDuration() time.Duration

DebounceDuration returns the watch debounce, defaulting to 30s.

type Watcher added in v0.3.0

type Watcher struct {
	// contains filtered or unexported fields
}

func NewWatcher added in v0.3.0

func NewWatcher(path string, onChange func(*Config), log *slog.Logger) *Watcher

func (*Watcher) Run added in v0.3.0

func (w *Watcher) Run(ctx context.Context) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL