Documentation menu

Configuration reference

Files and precedence#

FileScope and supported root keys
nid.yaml or nid.ymlProject configuration: groups, agents and default models. Looked up from the current directory toward its ancestors; .yaml wins when both exist in the same directory.
$NID_HOME/config.yaml (normally ~/.nid/config.yaml)User configuration: custom agents, global notify settings, default models and model prices. This is not a second place to declare groups or default arbitrary group settings.

Use nid up -f path/to/team.yaml to choose a file explicitly. Relative group dir values resolve against that file's directory. Project root, group, session, custom-agent and notification objects reject unknown keys; user config is read by section, so unrelated root keys there are ignored by the current readers.

A project that still has only a terminid.yaml or pawmux.yaml (or the .yml spelling) keeps working: nid reads it when no nid.yaml/nid.yml sits in the same directory (terminid.yaml before pawmux.yaml) and prints one line, terminid.yaml is deprecated; run `nid migrate` . nid migrate renames the file; see upgrading from terminid.

Custom agents resolve in this order: shipped presets, user definitions, then project definitions. A later definition replaces the whole earlier agent definition rather than merging individual fields. Built-in driver names cannot be replaced. A session records the definition it used; on resume a current definition for that name takes precedence, with the stored one as fallback.

up is idempotent: missing sessions spawn, stopped sessions resume, and running sessions stay running. Group settings from the settings table below apply to an existing group. Changing the file's goal, budget, policy, setup, or a running session's model is not a general live reconfiguration operation; use the appropriate commands or recreate the session. up --copy/--link replaces those lists for that invocation.

Project reference#

At the root, groups maps group names to the following objects; agents maps custom agent names to the agent definition; models sets default models for every group in the file and for nid spawn in the repository. All root keys are optional. Group and session names use letters, digits, ., _, -, start with a letter or digit, and have at most 64 characters.

Group fieldType / defaultMeaning
goalstring / unsetTeam objective; also supplies the lead's initial work.
policylead or peers / leadCoordination policy.
setupstring / unsetShell command run after local files arrive in a new worktree.
budgetUsdnumber ≥ 0 / no capCumulative USD cap on billed spend (plus subscription estimates with budgetIncludesEstimates); pauses automatic turns.
autoRestartboolean / trueDefault crash recovery for sessions; session value wins.
dirstring / .Repository relative to the config file.
sessionsnonempty array / requiredAgent shorthand strings or objects in the next table.

A session shorthand is agent[:name][@model]. An object supports:

Session fieldType / defaultMeaning
agentstring / requiredBuilt-in, preset or configured custom agent name.
namestring / agent nameNumbered from 2 when repeated; duplicate explicit names are rejected.
modelnonempty string / default models, else vendor defaultVendor model selection.
permissionsworktree, ask, yolo / group, user config, worktreeWhat the agent may do without asking; see permission profiles.
yoloboolean / falseLegacy shorthand for permissions: yolo.
sharedboolean / falseUse the main checkout instead of an isolated worktree.
autoRestartboolean / group defaultAllow one automatic resume after an eligible crash.
rolenonempty string / policy-derivedFirst session is lead under lead, otherwise worker, unless explicitly set.
promptstring / unsetAdditional first message.
billingauto, subscription, api / group, user config, autoBill this session to the agent's subscription login or an API key; see billing.
compressionboolean or object / group, user config, onThis session's compression overrides, merged per key over the group's; see compression settings.

Group settings#

These keys sit directly inside each groups.<name> object, alongside sessions. They are also accepted by nid group set <group> key=value and the dashboard. Minute values must be positive and at most 10080 (seven days), unless stated otherwise.

KeyType / defaultMeaning
stallMinutesminutes / 30Task time budget, overridden by a task's budgetMinutes (task --minutes).
stallGraceMinutesminutes / 10Grace after a stall warning before release/reassignment.
maxReassigninteger 0–100 / 2Shared automatic reassignment cap for stalls and long provider limits.
idleClaimMinutesminutes / 2An owner whose turn ended while it still holds a claimed task is re-prompted after this long idle; 0 turns it off.
idleClaimNudgesinteger 0–20 / 3Re-prompts (waiting 2, 4, 8 … minutes) before the idle owner's claims are released and the lead and you are told. Real work by the owner starts the count over.
limitReleaseMinutesminutes / 20Release claimed work when a provider limit is expected to last longer.
limitWarnPercentnumber 1–100 / 90Usage-window percentage at which to show a warning.
permissionsworktree, ask, yolo / user config, else worktreeDefault permission profile for the group's sessions.
crashResumestopped, continue / user config, else stoppedAfter the daemon stopped without a clean shutdown (crash, kill, reboot, logoff, power loss), the next start is unattended. stopped leaves the group's sessions stopped and tells you how to continue; continue (opt-in) resumes them and lets mid-turn sessions continue, which spends budget with nobody watching. See troubleshooting.
fallbackapi, off / user config, else offOpt-in: a rate-limited subscription session continues on its API key until the plan window resets, then switches back. See billing.
budgetIncludesEstimatesboolean / falseAlso count subscription estimates (not just billed API spend) against budgetUsd; see Billed spend vs subscription estimates.
copystring array / emptyRelative local files/directories/globs copied to new worktrees (max 200 entries).
linkstring array / emptyRelative directories/globs shared through symlinks/junctions (max 200).
reviewoff, auto, reviewers / offReview before integration; a nonempty reviewers list implies reviewers mode if this key is absent.
reviewersstring array / emptySession names or full group/name IDs eligible for reviewers mode.
notifyobject / global settingsNotification overrides and additional webhook targets; see below.
probject / unsetSaved PR metadata, normally maintained by land --pr and pr checks --watch.
billingmode or {default?, <agent>: mode} / user config, else autoSubscription login vs API key per agent (auto prefers the subscription); a session's billing wins. See billing.
modelsobject / file's top-level models, user configDefault models per agent type and role for sessions spawned into the group; merged over the file's top-level models (per agent, per role).
compressionboolean or object / user config, else onToken compression of nid traffic and the opt-in proxy, terse prompts, project memory and learn-after-done; false turns it off. See compression settings.
rotateobject / {mode: auto, contextPercent: 85}Context rotation for long-running sessions: mode (auto, compact, handoff, off), contextPercent (10–100) and maxTurns (positive integer, default none). See long-running sessions.

pr requires string url and integer number, with optional string base and number watchMinutes (0.01–1440). An absent watch interval means no background watch; the CLI's bare --watch uses one minute. Keep generated PR metadata unless you intend to change the watched pull request.

CLI values parse as JSON where possible. copy, link and reviewers also accept comma-separated text. An empty value (stallMinutes=) removes the stored override; API patches use null. YAML omits a field to use its default; YAML null is not a general reset syntax.

Default models#

A session runs the model it was spawned with (claude:lead@opus, a session's model:, the dashboard's Spawn form, nid spawn claude@sonnet). Without one, nid picks a default from models: blocks, so you can say "the lead on Opus, workers on Sonnet" once:

models:
  claude: sonnet                # per agent type
  codex: gpt-6-luna
  roles:
    lead: opus                  # a string: any agent in that role whose vendor uses the name
    worker: { claude: sonnet, codex: gpt-6-luna }   # or per agent

The same block is accepted in ~/.nid/config.yaml, at the top of nid.yaml, and inside a group (also nid group set <group> 'models={"claude":"opus"}'). Keys other than roles are agent names (built-in, preset or custom); role names are free text like a session's role. The most specific value wins:

OrderSourceRecorded as
1The session's own model: agent@model, session model:, dashboard Spawn, CLIsession
2nid.yaml models.roles.<role> (the group's, else the file's top level)project-role
3nid.yaml models.<agent> (the group's, else the file's top level)project-agent
4~/.nid/config.yaml models.roles.<role>global-role
5~/.nid/config.yaml models.<agent>global-agent
6The agent's own default: nid passes no modelagent-default

A role string names one vendor's model, so it is only used for agents of that vendor: claude takes Claude aliases (opus, sonnet, haiku, opusplan, default, ...) and claude-* ids; codex takes gpt-*, o<n>* and codex-*; gemini takes gemini-*; any other agent takes only names with no recognisable vendor (such as opencode's anthropic/claude-sonnet-4). Otherwise that step is skipped and the next one applies: with roles: { lead: opus } a codex lead falls through to models.codex. Values keyed by agent (models.codex, roles.worker.codex) always apply.

The model is chosen when a session is created and kept with it, together with its source (nid ls shows a model that came from configuration as configured until the agent reports the model it runs). Editing models: later affects new sessions only; change a running session's model with nid model. An invalid models: block does not stop spawning: the new session shows a warning naming the file and key, and that block is skipped (the next source in the table applies). Model names have no spaces or control characters, do not start with - and have at most 200 characters; whether an agent accepts a name is not checked here (nid models [agent] lists what each agent accepts).

User configuration#

# ~/.nid/config.yaml (or $NID_HOME/config.yaml)
agents:
  myagent:
    command: myagent
    args: [--acp]
    mode: acp
billing:                 # subscription | api | auto, or per agent (see billing.md)
  default: auto
  codex: subscription
fallback: off            # api: rate-limited subscription sessions continue on their API key
permissions: worktree    # worktree | ask | yolo: default permission profile (see security.md)
crashResume: stopped     # stopped | continue: after a crash/reboot, leave sessions stopped (default) or continue them
models:                  # default models per agent type and role (see Default models above)
  claude: sonnet
  roles: { lead: opus }
compression:             # token compression defaults (see compression-settings.md)
  enabled: true
  minTokens: 300
notify:
  desktop: true
  events: [approval, question, blocked, done, budget]
  webhooks:
    - url: https://example.com/nid-events
      kind: json
      events: [done, budget]
prices:                       # USD per 1M tokens; overrides or extends the built-in table
  gpt-6-astra: { input: 10, cachedInput: 1, output: 50 }
  my-local-model: { input: 0, output: 0 }

Model prices#

prices maps a model id to its list price in USD per 1M tokens: input (uncached) and output (including reasoning) are required; cachedInput (cache reads) and cacheWrite default to the input price. nid uses them to estimate what a turn would cost at API prices for agents that report tokens but no dollars (Codex, ACP agents such as opencode, Gemini CLI and Kimi). Ids are matched after dropping a provider prefix (openai/gpt-5 is gpt-5), a dated snapshot suffix and, for Claude, writing 5.5 as 5-5. Entries here win over the built-in table in packages/nid/src/pricing.ts (OpenAI, Anthropic, Google and Moonshot list prices as of the date in PRICES_AS_OF). Models in neither have no estimate: their turns show tokens only. An invalid prices section is ignored (the daemon logs why); the file is re-read when it changes.

Custom agent fields#

These fields are identical in project and user agents sections. Agent names start with a lowercase letter and contain lowercase letters, digits, _ or - (max 32). See custom agents for complete examples.

FieldType / defaultMeaning
commandnonempty string / requiredExecutable name or path.
modepty or acp / requiredTerminal driver or Agent Client Protocol over stdio.
argsstring array / emptyArguments always passed.
envstring-to-string map / emptyEnvironment overrides merged for the agent process.
promptPatternvalid nonempty regex / unsetPTY ready prompt, matched in multiline mode.
busyPatternvalid nonempty regex / unsetPTY working indicator; prevents prompt-based early turn completion.
quietMsinteger 100–600000 / 3000PTY silence timeout; extended tenfold while the busy indicator remains.
interruptnonempty string / ctrl-cPTY interrupt key (esc, ctrl-<letter>, enter, tab) or escaped bytes.
modelArgsstring array / emptyAdded only when a model is selected; use {model}.
modelsarray of ids or {id, description} / unsetModels the agent accepts, listed by nid models and used to check a model choice; it wins over what an ACP agent's sessions advertised.
mcpnone or object / unsetPTY MCP injection: args and/or file; ACP receives MCP through its protocol.
systemPromptprefix or none / prefixPrefix the first message with coordination instructions.
descriptionstring / unsetDisplayed agent explanation.
untestedboolean / falseMark unverified integration definitions in the catalog.

mcp.args is a string array. mcp.file requires path and optionally content; without content nid writes its standard mcpServers JSON. The path resolves against the worktree, but absolute paths and .. can write outside it; this is trusted configuration, not the constrained copy/link path mechanism. Choose a private path inside the worktree; inside-worktree files are git-excluded best-effort. Templates {configPath}, {cwd}, {session} and {model} expand in commands, arguments, environment values and MCP settings. Unknown template names stay literal. For PTY mode, absent mcp means no coordination tools; ACP's protocol injection does not depend on mcp.args or mcp.file.

Notification fields#

Global notify and group notify have the same shape:

FieldType / defaultMeaning
desktopboolean / trueNative desktop notifications; group value overrides global.
eventsevent-name array / allGroup filter replaces global; empty array disables matching events.
webhooksobject array / emptyGroup targets are added to global targets, deduplicated by URL.
webhooks[].urlHTTP(S) URL / requiredNotification destination; treat secret-bearing URLs as credentials.
webhooks[].kindslack, discord, json / inferredInferred from Slack/Discord host, otherwise JSON.
webhooks[].eventsevent-name array / allAdditional per-target filter.

Event names: approval, question, blocked, stall, limit, done, budget, review, pr, test. NID_NOTIFY_DESKTOP=0 disables desktop notifications regardless of YAML. Notification config reloads when the user file changes. nid notify config [group] shows effective settings with webhook URLs masked. The explicit notify test command bypasses event filters and deduplication so you can check configured destinations even when no normal event is selected.

Environment variables#

Set daemon settings before starting it; restart the daemon to change its environment. Most users only need NID_HOME, dashboard port selection or release credentials.

VariableDefault / behavior
NID_HOME~/.nid; database, runtime files, logs, worktrees and user config.
NID_PORT0: choose a free loopback daemon RPC port.
NID_DASHBOARD_PORT7420 with free-port fallback; a number pins it; off defers the listener until requested.
NID_NO_BROWSERAny nonempty value suppresses launching the browser.
NID_AUTO_TURN_LIMIT60 consecutive automatic turns before waiting for a human message.
NID_QUIET_MINUTES10 minutes without agent events while working; positive fractions accepted.
NID_QUIET_CHECK_MS30000 ms quiet/stall scan interval.
NID_PREEMPT_MINUTES3 minutes before interrupting an ACP turn for a waiting message; 0 disables.
NID_RESTART_MIN_UPTIME_MS60000 ms minimum process lifetime for automatic crash recovery.
NID_RESTART_WINDOW_MS600000 ms window that permits one automatic crash recovery.
NID_CRASH_RESUME0 keeps sessions stopped after the daemon itself died, even in groups with crashResume: continue. (By default they stay stopped anyway; see crashResume.)
NID_STORAGE_RETRY_MS30000 ms between write retries while the disk holding NID_HOME is full; no new turns start until a write succeeds.
NID_UNRESPONSIVE_MS20000 ms a daemon may go without answering before daemon stop, daemon restart and update treat it as hung (pinged every second).
NID_DAEMON_INFO_CHECK_MS5000 ms between checks that daemon.json is intact; the daemon rewrites it when it was deleted or corrupted.
NID_EVENT_RETENTION20000 events per session; positive integer, otherwise default. Pruning occurs in batches.
NID_MAX_EVENT_STREAMS64 simultaneous event streams.
NID_NOTIFY_DESKTOP0 disables desktop notifications.
NID_NOTIFY_DEDUPE_MS600000 ms deduplication interval per event key.
NID_NOTIFY_RATE_MAX6 notifications per target per rate window.
NID_NOTIFY_RATE_WINDOW_MS60000 ms notification rate window.
NID_REVIEW_DIFF_CHARS12000 characters of diff included in a review task; full diff remains available.
NID_LIMIT_JITTER_MSNormally random 2000–4999 ms delay after a rate-limit reset; nonnegative override.
NID_USAGE_CODEX_HOMECodex home whose session files feed nid usage (default CODEX_HOME, else ~/.codex); off disables. See usage.
NID_USAGE_REFRESH_MS300000 ms usage re-read interval while a session is limited.
NID_USAGE_OPEN_MARGIN_MS30000 ms: account data must be this much newer than a limit to clear it.
NID_NO_UPDATE_CHECK1 disables background update checks (normally at most once daily on a terminal).
NID_RELEASE_REPOCatsWithKeyboards/nid; owner/repo used by installer/updater/local publisher.
NID_RELEASE_APIhttps://api.github.com; updater API override, mainly for tests/custom hosting. API tokens go to this origin.
NID_GITHUB_APIPR/CI REST API override; also selects API transport instead of gh. Default derives from the repository host.
NID_VERSIONInstaller-only exact release version (same as --version / -Version).
NID_CHANNELInstaller-only release channel, stable, beta or alpha (same as --channel / -Channel); saved to $NID_HOME/update.json, which nid update --channel also writes.
NID_RELEASE_WEBhttps://github.com; release pages used for downloads when the API is unavailable or rate-limited (installers and updater). Never receives a token.
NID_GITHUB_TOKENOptional token for release lookups (preferred over GITHUB_TOKEN/GH_TOKEN); public releases need none.
NID_INSTALL_DIRInstaller destination; otherwise platform-specific default.
NID_NO_MODIFY_PATHInstaller: 1 leaves shell profiles/user PATH alone.
NID_ALLOW_LEGACY_DAEMON1 lets the daemon start while a terminid or pawmux daemon runs on the migrated ~/.terminid or ~/.pawmux (rolling back on purpose); otherwise it stops that daemon. See upgrading from terminid.

Releases are public and need no token. GITHUB_TOKEN/GH_TOKEN (or a logged-in gh, asked only when https://api.github.com refuses the anonymous request; never for a custom NID_RELEASE_API) raise the API rate limit, open private forks and are used for GitHub operations such as PRs. NO_COLOR suppresses terminal color.

Deprecated TERMINID_* and PAWMUX_* names#

Every NID_* variable above is also read under its old names: when NID_HOME is unset, TERMINID_HOME is used, then PAWMUX_HOME, and so on. The first read of each old name prints one deprecation line to stderr; NID_NO_DEPRECATION=1 silences it. When more than one is set, the NID_* value wins, then TERMINID_*. Rename them in your shell profile when convenient; the fallback will be removed in a later release.

Developer/internal overrides: NID_BIN selects the executable used for nid child processes (unset when testing source), and NID_FINGERPRINT overrides the source build identity. NID_SESSION and NID_SESSION_TOKEN are injected MCP session identity/credentials; do not set them for normal use (agents started before an upgrade still see TERMINID_SESSION/TERMINID_SESSION_TOKEN, which nid also sets for one release). __NID_BUILD__ and __NID_DASHBOARD__ are compile-time globals, not environment settings. Build/signing variables (NID_BUN, NID_SIGN, certificate and ASC settings) are covered in releasing; opt-in NID_LIVE_* and browser-test variables are covered in development.

nid.yaml and nid up#

Declare groups in a nid.yaml (or nid.yml) at the repository root and start them with one command:

groups:
  web:
    goal: Add dark mode to the settings page
    policy: lead            # lead | peers
    setup: pnpm install     # run in each new worktree
    budgetUsd: 20           # pause automatic turns once the group spends this much
    autoRestart: true       # resume crashed agents once (default; sessions can override)
    dir: .                  # repository, relative to this file (default: its folder)
    sessions:
      - claude:lead@opus    # shorthand: agent[:name][@model]
      - agent: codex
        name: impl
        permissions: ask   # worktree (default) | ask | yolo
        role: worker
      - agent: opencode
        prompt: Start by reading the test suite.
        shared: false

Session fields: agent (required: claude, codex, opencode, kimi, mock or a custom agent), name (default: the agent name, numbered when repeated), model, permissions, yolo, role, shared, autoRestart, prompt. Unknown keys are rejected with a readable error.

nid up                  # every group in ./nid.yaml (searched upward from the current directory)
nid up web              # just one group
nid up -f infra/nid.yaml -C some/dir

up is idempotent: it spawns missing sessions, resumes stopped ones and leaves running ones alone.

MCP isolation#

Agents default to mcp: project: nid's coordination server plus the repository's own servers. Claude reads .mcp.json in the session worktree; Codex reads .codex/config.toml and its mcp_servers table. If these files are untracked, include them in the group's copy setting so they reach each worktree.

ModeAvailable MCP servers
project (default)nid and the worktree's project servers
allnid plus the agent's normal inherited user/project servers and connectors
nonenid only; coordination still works

Set a global default in $NID_HOME/config.yaml (normally ~/.nid/config.yaml):

mcp: project

Override it per group or per session in nid.yaml:

groups:
  web:
    mcp: project
    sessions:
      - claude:lead
      - agent: codex
        mcp: none

Precedence is session, group, global, then project. nid doctor reports the global default. nid group set web mcp=none applies on the next agent start (including resume); running processes keep the selection they started with.

Claude uses --strict-mcp-config with a merged config, and ENABLE_CLAUDEAI_MCP_SERVERS=false suppresses fetched claude.ai connectors (Claude documentation). Codex explicitly disables inherited servers, plugins and app connectors using config overrides, retaining its login and other settings. An MCP table override alone does not clear inherited servers because Codex merges configuration tables. Native takeover uses the same selection for Claude and Codex.

ACP receives the selected project servers via its protocol. Other ACP/PTY agents may still load their own user configuration: isolation is best effort unless the agent provides a vendor-specific switch. Custom agents can supply it under agents.<name>.mcp.isolationArgs, which is added for project and none; {mcpMode} is available in argument/environment templates. Custom mcp: none inside an agent definition continues to mean that agent has no MCP integration.

Generated artifacts#

nid excludes common untracked artifacts from its automatic commits by default: __pycache__/, *.pyc, *.pyo, node_modules/, .venv/, /venv/, .pytest_cache/, .mypy_cache/, .ruff_cache/, .tox/, /target/, .next/, .turbo/, .parcel-cache/, /coverage/, .nyc_output/, .DS_Store, Thumbs.db, *.log, and .gradle/. Tracked files matching these patterns still have their edits and deletions committed. dist/ is intentionally not excluded.

groups:
  web:
    excludes: [generated/, '*.tmp'] # add gitignore patterns to the defaults
    # defaultExcludes: false        # keep only your own excludes above
    # excludes: false               # turn off all nid artifact filtering
    sessions: [claude:lead, codex]

nid group set web excludes=false changes the next status/staging operation. Patterns live in per-session files under $NID_HOME/run/excludes/, applied to nid's Git status and staging commands through -c core.excludesFile=.... This avoids changing Git's shared info/exclude, repository configuration, or the human's main checkout. Existing global Git excludes are preserved. Agents' own Git commands continue to use the repository's normal ignore rules, so their raw git status may still list artifacts that nid will omit from commits.

The /target/, /coverage/ and /venv/ defaults match only the worktree root; source directories with those names under src/ remain eligible for commits. After automatic staging (including an artifacts-only attempt), nid messages the agent about ambiguous omissions such as logs, root output directories and custom excludes. Routine caches, dependencies, .venv/, Python bytecode and OS metadata stay silent, as do repository and user ignores. Reports show up to 20 paths and the full count, and repeat only when the ambiguous omitted set changes during that session. They appear in verbose feeds only and never on the board. The files stay on disk. For an intentional file such as a log fixture, git add -f <path> includes it in the next commit; alternatively adjust excludes or defaultExcludes.

Local files in worktrees#

Keep local configuration available to agents without committing it. Set paths relative to the group's main checkout in nid.yaml:

groups:
  web:
    copy: [.env, "config/**/*.json"]
    link: [node_modules]
    setup: pnpm install
    sessions: [claude, codex]

copy accepts files, directories and globs (* and ? within a path segment, ** across directories, including dotfiles). link accepts directories, including directory globs. Links share the original directory, so writes through a link also change the main checkout. Copying is independent. Files arrive before setup, only in newly created isolated worktrees; resuming an existing session leaves its files alone. Existing files are preserved, tracked files are skipped, and missing sources produce notices in the session log. Paths must remain within the repository; Git metadata and source symlinks are skipped.

nid spawn claude codex --copy .env --copy "config/*.json" --link node_modules
nid up web --copy .env --link node_modules

The up flags replace the corresponding configured list for that invocation. Spawn flags persist as group settings for future sessions. Change them later through Manage group → Goal & budget → Group options, or group.update with settings: {copy: [".env"], link: ["node_modules"]} (empty arrays clear the lists).

Copied and linked paths are added to Git's info/exclude, resolved from the worktree and deduplicated. Git shares this exclude file across the repository's worktrees. Normal task completion and integration therefore leave these local files out of commits.

Group settings#

Daily-use features (stall handling, rate limits, worktree files, review gate, notifications) are configured per group. Put the keys at the top level of a group in nid.yaml, or change them on a running group:

nid group show web
nid group set web stallMinutes=20 copy=.env,config/local.json review=auto
nid group set web stallMinutes=          # empty value: back to the default

Values are JSON when they parse (notify='{"desktop":false}'); copy, link and reviewers also take comma-separated lists. The keys and defaults are listed under Group settings in the API reference; the dashboard's group settings dialog edits the same fields.

Notifications#

nid tells you when it needs you: an approval request, a question, a blocked task, a stalled or rate-limited agent, a group posting DONE, or a budget reached. It shows desktop notifications (Windows toast through PowerShell, macOS osascript, Linux notify-send; nothing to install, and silently skipped when unavailable) and posts to webhooks. Configure targets for every group in ~/.nid/config.yaml:

notify:
  desktop: true
  webhooks:
    - url: https://hooks.slack.com/services/T000/B000/XXXX     # Slack
    - url: https://discord.com/api/webhooks/123/abc            # Discord
    - url: https://example.com/hooks/nid                    # anything else gets plain JSON
      kind: json
      events: [done, budget]                                   # optional filter per webhook

or per group (notify: in nid.yaml, nid group set web notify='{"desktop":false}', or the dashboard's Manage group → Notifications dialog). Repeated events are de-duplicated and each target is rate-limited.

nid notify test [group]     # send a test notification to every target and show the result
nid notify config [group]   # effective targets (webhook URLs masked)
nid notify ls [group]       # recent notifications and their delivery status