Skip to content

Latest commit

 

History

History
554 lines (485 loc) · 74.6 KB

settings.md

File metadata and controls

554 lines (485 loc) · 74.6 KB

Part of the Intent JSON-RPC protocol docs — §5.12 settings.*.

Routing-only workspaceId? additions below are prepared contract fields, optional on direct daemons and required for future forwarded workspace calls; existing scope and results are unchanged.

5.12 settings.*

The daemon owns the settings that affect server-side behavior and lets thin clients read/mutate them over the wire. These methods are global — like specialist.list / repo.list they do not require workspaceId (§3.6).

Method Params Result
settings.list workspaceId? { settings: SettingDefinitionWithValue[], revision: number } (sensitive values redacted; TOML-backed entries carry origin)
settings.get path (req), workspaceId? { path, value, definition, origin?, revision: number } — -32602 if path is unknown
settings.update changes (req, array of { path, value, reason? }) { applied: [{ path, value, origin? }], revision: number }; triggers settings:changed
settings.reset path (req) { path, value, origin?, revision: number } (restores defaultValue, except for absent-means-auto keys — see below — where it removes the key and returns value: null) — -32602 if path is unknown

SettingDefinition** shape:**

interface SettingDefinition {
  path: string;            // dotted key, e.g. "server.wsApi.port" or "sourceControl.github.token"
  label: string;          // human-readable name
  description: string;    // help text
  category: string;       // grouping, e.g. "server", "sourceControl", "providers"
  type: "boolean" | "number" | "string" | "enum" | "object";
  enumValues?: string[];  // present when type === "enum"
  min?: number;           // numeric bound (type === "number")
  max?: number;           // numeric bound (type === "number")
  defaultValue?: unknown; // value used by settings.reset (informational only for absent-means-auto keys, see below)
  sensitive?: boolean;    // when true, value is redacted in settings.list / settings.get
  tokenImpact?: string;   // approximate prompt-token cost of the gated feature, e.g. "~620 tokens/session"; presence-detected (absent when unannotated)
}
// SettingDefinitionWithValue = SettingDefinition & { value: unknown }  // current value (redacted if sensitive)

changes entries use the AppSettingChange shape { path, value, reason? } (reason is an optional free-text audit note). settings.update validates each change against its definition (type / enum / min / max) and persists atomically; an unknown path or a value failing validation yields -32602 and the whole batch is rejected (nothing applied). On success it emits a settings:changed notification (§6.5) carrying the applied { path, value, origin? } pairs (sensitive values redacted).

Placeholder-preserve on write (intent-hq/intentd#1738, fixes intent-hq/intent#4383) — because settings.list / settings.get redact every sensitive setting that has a stored value to the ******** placeholder, a client that submits its whole settings form back through settings.update echoes that placeholder for every secret it did not touch. For a sensitive path, settings.update therefore resolves the value per change: the placeholder with a stored secret is a no-op for that path — the secret is left as is (no store write), but the entry is still echoed (redacted) in applied and in the settings:changed notification, so unlike a literal that already matches the stored value it is not dropped as unchanged (a placeholder-only batch still advances the revision and emits the event); the placeholder with no stored secret is -32602 (the message names the offending path) and the whole batch is rejected atomically — a sibling non-sensitive change in the same batch is not applied and the placeholder is never stored; any other literal sets/replaces the secret as before. The presence check runs in the validation pass, before the atomic apply, so a rejected batch leaves config.toml, the DB, and the secret store untouched. Non-sensitive paths are unaffected. Responses stay redacted; no wire-shape change. Mirrors the mcp.servers.update rule (§5.22). Secrets that were already overwritten by an earlier placeholder echo hold the literal ********; the daemon cannot recover them — they must be re-entered once.

Provider/model default selection is one logical mutation. When a selection changes the provider, clients MUST send model.defaultProvider (intent-hq/intentd#1648; the retired providers.active key is migrated out of config.toml at boot, intent-hq/intentd#1658) and the complete updated model.providerDefaults object in one settings.update batch. Object-valued settings are whole-value replacements, so the client must preserve entries for providers it is not changing. Splitting the two paths across requests exposes an invalid intermediate default to agent creation and other clients. The daemon validates and commits the whole batch, or rejects it without changing either path, advancing the revision, or emitting an event.

revision is an unsigned, daemon-issued ordering watermark. It starts at 0 for each daemon process and strictly increases after every committed settings mutation, including wire updates/resets and accepted config.toml live reloads. A rejected atomic batch does not advance it. The successful mutation response and its settings:changed event carry the same revision. An empty/tolerated-only update, an update whose entries already match the stored value and origin, or a reset whose value is already absent is a no-op: it returns the current revision and emits no event (settings.update.applied also omits unchanged entries). Every authoritative read (settings.list and settings.get) carries the revision at which its values and origins were read. Clients should ignore reads, mutation results, or events older than their highest revision for the current backend connection. Because revisions are process-local, clients must clear that watermark whenever the backend connection changes or reconnects, then seed it from the next settings.list snapshot. An event or mutation result at the watermark is a valid duplicate view of the same commit and may be applied idempotently; only a lower revision is stale. A client talking to an older daemon that omits these additive fields may retain its legacy arrival-order behavior, but MUST NOT compare a missing revision with a numbered watermark. See frontend reconciliation for connection-generation handling.

Storage & the origin field. Non-secret human-editable settings are TOML-backed: they persist in <data_dir>/config.toml, layered defaults < config.toml < startup flags/env. For TOML-backed paths, settings.get (and each settings.list entry) carries an additive origin field naming the layer the effective value came from: "default" (absent from the file), "file" (explicit in config.toml), or "flag" (pinned at boot by a startup flag / env var, e.g. --insecure, INTENTD_TCP_PORT). Secrets and the opaque machine-state blobs (repos.known, workspace.changeHistory, workspaceInitializer.state, hardwareConsole.state, permissions.rules, userRules / workspaceRules, endUserRules, voice.vocabulary) have no origin — they never live in config.toml (secrets stay in .secrets.json, state blobs stay in SQLite). Successful update/reset results and settings:changed entries carry the post-commit origin for TOML-backed paths as well. This field is required for convergence even when the effective value is unchanged: explicitly writing a schema default changes its origin to "file", while resetting that explicit value changes it back to "default". settings.update on a TOML-backed key rewrites config.toml atomically (temp file + rename, comment/layout-preserving); external hand-edits of config.toml are live-reloaded (strict re-parse, debounced; invalid content keeps last-good values) and emit the same settings:changed notification. A key pinned by a startup flag is read-only over the wire while pinned: settings.update / settings.reset on it yields -32602 with a message naming the overriding flag ("overridden by startup flag …").

defaultValue for absent-means-auto keys. For a TOML-backed key whose absence is the default and resolves to a host-derived value — today agents.memoryBudgetMb, where the absent key means auto (RAM − 8 GB) / 2 (min 4 GB) — defaultValue advertises the effective auto budget in MB for this host so a client can render "Auto (N MB)" and tell auto apart from an explicit 0 (off). It is informational: settings.reset removes the key from config.toml and returns value: null with origin: "default", and settings.get / settings.list keep reporting value: null for the absent key — the advertised number is never persisted or echoed back as the value. Writing that number explicitly via settings.update pins it as a "file" value that no longer tracks the host's RAM.

Quick-action effort settings (additive). The following TOML-backed settings live under [quickActions], alongside the existing model settings. Model values remain bare model-id strings; effort is never encoded into a model id.

Setting Value Default / clear
Shared effort: quickActions.defaultReasoningEffort Optional string; shared effort preference for one-shot quick actions Unset; settings.reset clears it. An empty or whitespace-only string reads as unset.
Per-type effort: quickActions.typeReasoningEffortOverrides Object mapping action type to effort string; conventional keys commit, pr, review, fast, with free-form keys allowed like typeOverrides {}; remove an entry or write a blank string to inherit shared effort for that type. settings.reset clears the whole map.

Non-blank effort strings retain their spelling in storage. Providers own the vocabulary; settings writes validate the string/map shape, not a fixed effort enum or the currently selected model's capabilities. A non-string map member rejects the entire settings update with -32602. At execution, matching is case-insensitive after trimming, and the advertised provider spelling is used. Stale saved values unsupported by the resolved model are skipped with a daemon warning; they do not make an otherwise valid quick action fail. Resolution is request effort → compatible per-type effort → compatible shared effort → provider default, independent of model inheritance; see §5.32 for capability checks and explicit-request errors. model.defaultReasoningEffort is never a fallback here. With both new settings absent, requests without effort retain their existing execution behavior.

Clearing and model changes. Clearing only a model override must not clear that action's effort override; clearing only effort must not change the model. An action's combined reset clears both overrides. Resetting all quick-action settings clears the shared model/effort, both override maps, and the provider snapshot cache. Clearing per-type effort means inherit, not force provider default while a shared preference is set. On a model change, retain saved effort preferences but recompute their compatibility: only supported choices may appear as effective in the picker. A missing/cold model catalog must not erase saved preferences or invent effort options. The inherited model may be the shared quick-action model or the provider's advertised default; this is not the ordinary agent default-model chain.

Provider snapshots. quickActions.providerSettings remains an opaque client-owned object keyed by provider id, not an execution precedence tier. Each client snapshot extends its existing { defaultModel, typeOverrides } with optional defaultReasoningEffort and typeReasoningEffortOverrides members of the same shapes above. Missing effort members in older snapshots mean unset / {}. Clients must snapshot and restore model and effort together on provider switches; a provider with no snapshot starts with all quick-action preferences unset. Persist the outgoing snapshot, restored active quick-action fields, model.defaultProvider, and any changed model.providerDefaults together in one atomic settings.update batch, preserving other providers' snapshots and map entries. Never carry one provider's effort into another provider's active preferences. A saved unsupported effort may remain in its own provider snapshot for a later switch back; it must not appear as an applied choice. Hydration and partial settings:changed reconciliation must handle the new fields without replacing unaffected siblings or writing defaults back over saved values.

Provider Fast mode preference.

providers.fastMode is a non-sensitive, TOML-backed object setting in category providers, with defaultValue: {}. It is a daemon-global map of canonical provider ids to booleans, initially supporting claude-code and codex. Missing entries mean off, independently for each provider. The preference survives daemon restart and is shared by every workspace and client connected to that daemon; it is not an agent, workspace, model, or reasoning-effort override.

// settings.update — complete replacement of the map, preserving the other provider
{"changes": [{"path": "providers.fastMode", "value": {"claude-code": true, "codex": false}}]}

The value must be an object whose members name providers advertising supportsFastMode: true in providers.catalog (§5.38), and whose values are booleans. Arrays, null, non-booleans, aliases, unknown ids, and unsupported providers are -32602; validation rejects the entire update batch before persistence. This is a whole-value replacement, not a deep merge: clients preserve the other provider's entry when changing one toggle. There are no independently addressable dotted child settings such as providers.fastMode.codex. settings.reset on providers.fastMode restores {} and origin default, removing the explicit map from config.toml; both providers then resolve to off. Update, reset, reload, revision, and settings:changed semantics are otherwise the same as other TOML-backed settings above. An invalid file edit retains the last-good settings. Provider-native configuration files must not be modified.

Clients show this control only when the setting is present in settings.list and that provider's catalog row has supportsFastMode === true. An omitted capability means unsupported (including older daemons); do not send the unknown setting to such a daemon or substitute renderer-local persistence. Older clients may ignore the additive setting and catalog field.

Application boundary. Saving acknowledges the stored preference, not the tier of an in-flight turn. The daemon reads the latest preference during pre-prompt setup for each new turn, after resolving the provider session and selecting its model/effort. This includes warm reuse of a live adapter, fresh session/new, session/load/resume, persistent and delegated agents, and one-shot prompts. A resumed session must use that preference, including explicit off, rather than inheriting its earlier tier. Off must actively undo an inherited enabled tier; omitting an enable flag is not sufficient. Model/catalog-only probes need not request Fast mode.

Apply the session config control at the existing safe pre-turn boundary, before submitting the prompt. A settings update/reset must not mutate or interrupt an in-flight turn, restart intentd, or recycle adapters. An update committed after a turn has sampled its preference is picked up by the following turn. UI guidance should say that changes apply on the next turn; restarting is not required. If a model changes, check eligibility for the newly selected model and apply the latest preference without changing that model or its reasoning effort.

Eligibility and failures. Fast mode requests a service tier; it neither guarantees lower latency nor proves account/model access. Preserve the selected model and reasoning effort. For unsupported models, keep ordinary model behavior and retain the saved preference for future eligible sessions. Explain model/account restrictions and potentially increased usage in the UI, and surface available provider refusal or application errors without reporting them as an active Fast tier or rewriting the saved preference. A settings write can succeed even if a later session cannot use the tier. If applying off fails for an eligible session, surface the failure and do not silently submit its prompt with an inherited Fast tier.

For Claude, the adapter omits the fast option unless the selected model advertises supportsFastMode. Its setSessionConfigOption rejects an absent option with Unknown config option: fast before calling the SDK. For an eligible model, the fast-mode branch only calls applyFlagSettings({ fastMode: enabled }); it does not call setModel or change effort. The daemon must skip both on and off controls when the option is absent, allow ordinary use of the selected model, and not claim that off was acknowledged. Recheck eligibility and apply the latest preference before a later eligible prompt. Never invoke the native /fast command, select a different model, or retry on another model to enable Fast mode. Account/managed-policy rejection retains the selected model and saved preference; use the provider's reported disabled reason or error to explain ordinary-tier fallback or refusal.

Verified ACP controls. These are adapter integration requirements, not new Intent client RPCs. Both pinned adapters support session/set_config_option after session/new or session/load; use the returned option for the selected model and the select fallback below (or its negotiated boolean representation):

Provider Pinned adapter configId On / off values Effect
claude-code @agentclientprotocol/claude-agent-acp@0.81.1 fast "on" / "off" Calls SDK applyFlagSettings({ fastMode: true/false }); option exists when the model advertises supportsFastMode.
codex @agentclientprotocol/codex-acp@1.13.1 fast-mode "on" / "off" Records the preference and sends turn/start.serviceTier: "fast" only for a model whose additionalSpeedTiers contains fast; off or an unsupported model sends explicit null to clear the tier.

For example, Claude off is {"sessionId":"…","configId":"fast","value":"off"}. Apply the latest preference before each new turn, including after load; a resumed session's initial option value is not authoritative for the daemon preference. Claude's SDK reports a session opt-in requirement; do not infer opt-in solely from native settings files. Codex reads CODEX_CONFIG JSON once at ACP process startup and forwards it on thread start/resume; native -c argv is not the adapter configuration transport. Preserve existing daemon-owned Codex policy when composing spawn configuration; do not replace it with a tier-only object. No provider-native config-file write is needed by either control.

Source evidence is pinned to Claude ACP's session control and lifecycle, its on/off and SDK-state tests, Codex ACP's tier resolver, its session start/load configuration, and its explicit-clear tests. Reverify these paths when changing the pins; Codex ACP's underlying Codex dependency is a semver range (^0.156.1), not an exact runtime pin.

BE-exposed setting paths. Only settings that affect daemon behavior are exposed:

sourceControl.gitlab.instanceBaseUrl is an optional string for the full logical HTTPS instance root, including its port and installation prefix. When it is unset, the instance root derives from sourceControl.gitlab.host. When set, both settings must name the same authority; for example, host: "gitlab.example:8443" and instanceBaseUrl: "https://gitlab.example:8443/forge". The prefix is part of repository and credential identity, not a project namespace. The separate sourceControl.gitlab.apiBaseUrl transport override cannot supply or change this logical identity. The prepared checkout contract retains the exact instance and credential continuity through browsing, warming and native creation; a same-host prefix change is not authority to reuse the previous connection.

The prepared full-instance auth operations accept this root as instanceBaseUrl. A successful PAT or device connection publishes host and root together; clients must not perform two independent settings writes to emulate connection setup.

  • Providers / agents: providers.active (deprecated: superseded by model.defaultProvider; kept in the catalog during the deprecation window but read-only on the wire — settings.update tolerates-and-ignores it before the read-only rejection would fire, so old clients never fail a batch), model.defaultProvider, providers.enabled, providers.paths.{auggie,claude-code,codex,…},model.default, model.providerDefaults, model.defaultReasoningEffort, quickActions.defaultModel, quickActions.typeOverrides, quickActions.defaultReasoningEffort, quickActions.typeReasoningEffortOverrides, quickActions.providerSettings, specialists.default, specialists.dir. model.defaultReasoningEffort (intent-hq/intentd#970) is an optional string persisted in config.toml under the [model] table as defaultReasoningEffort — the fallback reasoning effort for newly created agents, stored as-is (providers own the level vocabulary; the daemon never normalizes it) with a blank or whitespace-only value reading as unset (default: unset). It is the last rung of the creation-time reasoning-effort chain (§5.5 "Creation-time reasoning-effort resolution"), applying only when no explicit param / specialist model-option / specialist frontmatter effort decided the level and the session's model itself resolved from the settings chain; a level the resolved model's cached effortLevels provably does not list is dropped with a daemon warn log rather than rejected (§5.11). Agent model resolution walks model.providerDefaults[provider] → model.default (the settings-chain step of the daemon-side creation-time resolver, §5.5 — specialist frontmatter model takes precedence over this chain, and every result is provider-guarded). The quickActions.* keys (intent-hq/monorepo#1729) scope only to single-shot quick actions (commit messages, PR descriptions, quick tasks) and are never consulted for an agent session, delegated ones included; they were named backgroundAgents.* before that rename, and the old paths are retired — gone from the catalog (settings.list never advertises them; settings.get / settings.reset yield -32602) but tolerated-and-ignored by settings.update, while a config.toml still carrying [backgroundAgents] has its values carried over once at boot — per member (defaultModel / typeOverrides / providerSettings are applied individually, so one malformed legacy value never discards its valid siblings), into each quickActions.* key still at its schema default, so an already-migrated or deliberately re-picked value is never clobbered, and a legacy member with no quickActions.* counterpart is dropped with a warning — before the legacy table is stripped. The effective default provider is the dedicated model.defaultProvider key (intent-hq/intentd#1648): registry-validated and whitespace-trimmed on read, so a stale or mistyped value reads as unset. The deprecated providers.active key is never consulted — a one-time boot migration carries a legacy value into model.defaultProvider (only when the target is still unset and the value names a registered provider) and removes providers.active from config.toml regardless, retrying next boot on a failed write (intent-hq/intentd#1658). There is no positional fallback to the first registered provider — with the key unset, resolution that falls through entirely fails loudly at the caller (intent-hq/monorepo#3044; no provider carries a hardcoded default designation). Model-valued keys (model.default, quickActions.defaultModel, and the map values of model.providerDefaults / quickActions.typeOverrides) must be bare model ids: settings.update rejects a :-bearing value with -32602 (<path>: model values must be bare model ids without ':' (got "<value>"); set the provider via model.defaultProvider instead — intent-hq/intentd#1647); legacy compound values already on disk are split on read rather than rejected (intent-hq/intentd#1651). The former model.workspaceOverrides key is retired: it is gone from the catalog (settings.list never advertises it; settings.get / settings.reset yield -32602), and settings.update rejects it as an unknown setting with -32602, including in mixed batches: no entries are applied or published in settings:changed. Boot compatibility remains for old persisted state: any stale SQLite row is deleted, and a legacy config.toml key still parses and is stripped with its value discarded. These terminal-state cleanup paths do not make the setting writable again. specialists.default (optional string, TOML-backed under [specialists] as default; default: unset) is the id of the specialist applied when none is chosen — a client-consumed setting: the daemon stores and serves it but never consults it when creating agents. The desktop FE's task-note Run path reads it: Run spawns the note's agent as the configured specialist when the id resolves to a pickable entry in the resolved specialist roster (visible to the client and not hidden, §5.11), and falls back to implementor when the key is unset or does not resolve. specialists.dir (optional string — settings.update on it always yields -32602 (read-only), while settings.reset yields -32602 only when the value is startup-pinned and otherwise clears a file-written value, removing the key from config.toml) reports the base-tier replacement directory for specialist resolution (§5.11 "Base-tier replacement mode"): it takes a value via the INTENTD_SPECIALISTS_DIR startup pin (or the intentd serve --specialists-dir flag, which folds into the env var pre-runtime — the flag wins over an inherited env value), else a hand-written [specialists] dir in config.toml; unset or empty means no replacement.

  • Workspace / git: workspace.branchPrefix, workspace.worktreesLocation,workspace.sshKeyPath (string — filesystem path to the key, not key material; the real secret is the key file on disk, so the value is read back verbatim by the FE git-env consumer), workspace.defaultShell, workspace.autoCommit, workspace.cowIsolation (boolean, default false — CoW workspaces + per-agent sandboxes: workspace.create/workspace.duplicate provision the checkout as a standalone CoW clone instead of a linked worktree (§5.1), and agent.delegate defaults isolation to "cow" when the param is omitted (§5.5); ignored by cache-hydrated creation and by workspace.duplicate of a standalone-checkout source, which are always standalone (§5.1); consulted only at provisioning time — the resulting checkoutMode is immutable per workspace (§5.1); requires CoW filesystem support on the workspaces root — the FE gates the toggle on Workspace.cowSupported).

  • MCP: mcp.enableUserServers, mcp.disabledServers, mcp.servers (sensitive).

  • Server / transport (new in intentd): server.socketPath,server.bindAddress (string or string array (intent-hq/monorepo#3314): a single IP ("127.0.0.1", the default) or a list of IPs (["192.168.1.7", "100.64.0.3"]) — one TCP listener per entry on the same port, bind is all-or-nothing. Loopback is always bound in addition (intent-hq/intentd#1695): at boot and on every runtime listener (re)start the daemon appends 127.0.0.1 to the effective bind set unless the configured set already carries 127.0.0.1 or an unspecified address (0.0.0.0/::), so a LAN-only value such as ["192.168.1.7"] still serves clients on the daemon host and the tunnel sidecar's 127.0.0.1 forward; the persisted value is never rewritten (settings.get returns exactly what was written). Validation: every entry a valid IP, no duplicates after canonicalizing v4-mapped IPv6, an unspecified address (0.0.0.0/::) only as the sole entry, list non-empty. The catalog advertises the key's type as "string" for FE back-compat, but settings.get/settings.list return the value in its written shape — clients must tolerate an array; a string-typed save over an array value replaces the whole list), server.port (legacy port key — preserved, validated, and readable/writable for compatibility, but unused by listeners; listed as "Legacy port (unused)" and omitted from new config templates. Existing values are never copied or migrated to server.wsApi.port), server.wsApi.enabled, server.wsApi.port (numeric; absent config means unassigned, then fixed after the first successful WSS start — see WSS port assignment), server.tunnel.enabled (boolean, default false, TOML-backed under [server.tunnel] (intent-hq/intentd#1618) — run the bundled tailcat tunnel sidecar forwarding tunnel traffic to the local WSS port. The tunnel requires the WSS listener up, so in a settings.update batch the tunnel toggle applies after server.wsApi.enabled (a batch enabling both starts the listener first). The sidecar's stable tc… address — derived from a key persisted in the daemon data dir, so it survives daemon and sidecar restarts — rides pairing.getInfo / server.pairingInfo / system.status as the additive tcAddress field and the tc= pairing-URI param (intent-hq/intentd#1623; §5 fast-path catalog). The tailcat tunnel is unrelated to the /tunnel loopback port-forwarding endpoint, §1.4. Live-applied: enabling starts the sidecar as part of the settings.update (a sidecar that fails to start fails the update with -32603), disabling stops it; disabling server.wsApi.enabled also stops a running tunnel, since it forwards to that listener — without touching the persisted server.tunnel.enabled, so re-enabling the listener brings the tunnel back (non-fatal, like the boot auto-start; skipped when the batch carries its own tunnel toggle)), server.tunnel.derpUrl (optional string, TOML-backed under [server.tunnel] — DERP map URL for the tailcat tunnel: passed to the sidecar as --derpmap-url, so the value must serve a DERP-map JSON document (which resolves the relay region for a self-hosted relay setup), not a bare relay endpoint; it also feeds key generation so region discovery uses the configured map. Empty/unset uses tailcat's default relay. Live-applied: a change restarts a running sidecar so the new relay takes effect immediately; when the tunnel is not running the value is persisted for its next start), server.tunnel.only (boolean, default false, TOML-backed under [server.tunnel] — accept tunnel-forwarded traffic only: the WSS listener binds loopback, refusing direct LAN connections. Live-applied: a change restarts a running WSS listener with the new bind set (mirroring the server.bindAddress hook). Self-termination guard: enabling it from a direct TCP (WSS) connection is rejected with -32602 before anything persists — the loopback rebind would drop the caller's own connection — so flip it over UDS. The guard keys on the raw transport (like the server.wsApi.enabled disable guard), so tunnel-forwarded connections — which arrive at the listener over TCP — are rejected too), server.tls.enabled, server.auth.enabled,server.auth.token (sensitive; read-only / regenerate), server.originAllowList, server.maxOutstandingRpcs (number, default 256, min 0, max 100000, TOML-backed under [server] — the daemon-wide cap on outstanding slow-path RPCs shared across every connection and both transports; over-limit requests are rejected with -32011 "Server overloaded" (§9) and 0 disables the cap. Read once at boot: a change applies on daemon restart). A successfully started daemon always serves UDS; enabled-WSS boot failure exits the daemon (see WSS port assignment). The TCP/WSS listener is toggled at runtime by server.wsApi.enabled (the former server.listenMode key is retired — a config.toml still carrying it boots, is discarded, and is stripped from the file).

  • Source control (new in intentd, provider-agnostic): sourceControl.activeProvider (enum,**default **github; v1 ships only github), sourceControl.github.tokenSource(auto|env|gh-cli|explicit; default auto — secrets store → env → gh CLI), sourceControl.github.token (sensitive),sourceControl.github.apiBaseUrl (GitHub Enterprise support), sourceControl.github.exposeGitCredentialToChildren (boolean, default true — inject the daemon-managed GitHub credential into child process environments (PTY terminals, agent provider shells) as a scoped github.com-only credential helper; never as a raw GITHUB_TOKEN/GH_TOKEN). Per-provider config is namespaced assourceControl.<provider>.* so future hosts slot in as sourceControl.gitlab.*,sourceControl.bitbucket.*, etc. (replaces any flat github.* keys). GitLab (v10.5, §5.27 "Provider-generic auth — sourceControl.*"): sourceControl.gitlab.host (string, default gitlab.com — the bound instance as a bare host[:port], no scheme; written by a successful sourceControl.connect { provider: "gitlab" } and read as the default host of every gitlab sourceControl.* call), sourceControl.gitlab.oauthClientId (string, default "" — the public OAuth application id used for the device authorization grant against sourceControl.gitlab.host; empty ⇒ deviceGrantSupported: false unless the host is gitlab.com and the daemon's built-in client id is non-empty), sourceControl.gitlab.apiBaseUrl (string, optional test seam — overrides the API origin the daemon calls for the bound host, e.g. a mock server; never changes the reported host), sourceControl.gitlab.token (sensitive — the GitLab credential the device grant or PAT connect persists to the secret store under account sourceControl.gitlab.token, the first slot of the GitLab resolution chain ahead of the GITLAB_TOKEN env fallback; sourceControl.revoke { provider: "gitlab" } deletes it).

  • Identity (v10.8, §5.48 "Identity model"): identity.provider (enum github | gitlab, nullable; default null) — which connected forge is the primary principal's identity. null ⇒ implied: the one connected forge, or, with both connected, the current identity (else github; the FE prompts the user to choose). Writing a value is an explicit re-key of the primary principal's identity triple (GET /user on GitHub, GET /api/v4/user on GitLab) that publishes principal:identity-changed (§6.5); a value naming a forge that is not connected leaves the primary unlinked until it connects. Invites already pinned to the old identity are unaffected; new invites default their pinProvider / pinHost to the new one. Non-secret; settings:changed fires as for any key.

  • Linear (new in intentd): linear.token (sensitive) — the Linear API key, persisted to the daemon's file-backed secret store (~/intent/.secrets.json, 0600) under account linear.token, the exact entry the linear.* namespace's secret-store-first auto token resolution reads (§5.28), so settings.update on this path is the FE "connect Linear" flow.

  • Sentry account (new in intentd): accounts.sentry.token (sensitive) — the Sentry API tokenused by the sentry.* namespace (§5.29); accounts.sentry.organization (string) — the Sentryorganization slug (non-secret companion).

  • Voice (new in intentd): voice.provider (enum: elevenlabs | openai, default elevenlabs) — the transcription provider voice.transcribe uses when the call carries no per-call provider override (§5.41); voice.language (string, optional — no default) — the default transcription language hint (ISO-639-1 code, e.g. "en") applied when a voice.transcribe call carries no per-call language (or a blank one — per-call values are trimmed and blank behaves like omitted; §5.41 "Language resolution"; TOML-backed under the [voice] section of config.toml, like voice.provider; unset or blank means provider auto-detection); voice.openai.model (enum: gpt-4o-transcribe | gpt-4o-mini-transcribe | whisper-1, default gpt-4o-transcribe) — the transcription model the OpenAI provider posts (§5.41 "Providers"; TOML-backed under the [voice] section of config.toml, like voice.provider); voice.vocabulary (object, non-sensitive — a JSON string array; default = ["Intent"], see §5.41 "Context mapping") — the user-editable vocabulary biased into every voice.transcribe call, read per call and merged ahead of context.keyterms (§5.41; SQLite-backed like the other opaque bags — no origin; a stored value exactly matching the retired 17-term seed default is deleted on daemon start so the new default applies — user-modified lists are never touched); voice.workspaceVocabulary.maxTerms (number, default 50, min 0, max 100; v5.1) — the cap on the auto-derived workspace vocabulary injected into voice.transcribe calls carrying a workspaceId and served by voice.getWorkspaceVocabulary (§5.41 "Workspace vocabulary"; TOML-backed under the [voice] section of config.toml, like voice.provider; 0 disables workspace vocabulary entirely — no derivation, no injection; a change takes effect on the next derivation); voice.elevenlabs.apiKey (sensitive) and voice.openai.apiKey (sensitive) — the provider API keys, persisted to the daemon's file-backed secret store (~/intent/.secrets.json, 0600) like linear.token. Key resolution is secret store first, then the ELEVENLABS_API_KEY / OPENAI_API_KEY environment variable fallback; the keys are never logged, echoed, or returned over the wire (redacted in settings.list / settings.get like every sensitive path).

  • Persisted policy & rules (new in intentd): permissions.rules (object) — persisted commandallow/deny/ask entries; userRules (object) — global user prompt-rule content;workspaceRules (object) — workspace-scoped prompt-rule content. Each is an opaque bagvalidated by shape only; downstream consumers own the internal schema.

  • Cross-workspace repos & history (new in intentd): repos.known (object) — the daemon-owned known-repository list; workspace.changeHistory (object) — per-workspace diff-history bags. Both are non-sensitive; the daemon persists the JSON opaquely.

  • Workspace initializer (new in intentd): workspaceInitializer.state (object, non-sensitive, default {}) — persisted home-screen workspace-initializer form state, opaque bag owned by the FE.

  • Hardware console (new in intentd): hardwareConsole.state (object, non-sensitive, default {}) — persisted hardware-console device configuration (key assignments, action mappings, prompt-picker limit), opaque bag owned by the FE.

  • Context engine (new in intentd): context.enabled, context.auggiePath, context.allowIndexing.

  • Storage / runtime (new in intentd): storage.dataDir, workspaces.root, logging.level,agents.maxConcurrent, agents.maxConcurrentAdapters (number, default 6, min 1, max 64, TOML-backed under [agents] — the daemon-wide cap on concurrently live ephemeral ACP adapters: the one-shot agent.completeOnce completions (§5.32) and model probes (§5.30) that spawn a provider-CLI chain without holding an agents.maxConcurrent slot. Over-limit calls queue instead of spawning and are rejected with -32603 adapter-busy (§9) if their own timeoutMs expires while queued. Deliberately no 0 = unlimited escape hatch, unlike server.maxOutstandingRpcs — an unbounded adapter spawn is the failure this bound fixes, and an out-of-range value is rejected rather than booting uncapped. Read once at boot: a change applies on daemon restart), agents.idleReapMinutes, agents.maxTopLevelAgents (number, default 20, min 1, no max, TOML-backed under [agents] — within v7.5: the per-workspace cap on live top-level (parentless, depth-0, non-deleted, non-retired) agents, enforced on the top-level-create path only (ws.agent.create({ topLevel: true }) — originally ws.agent.spawnPeer, merged into create within v8.1; §5.5 "Peer agents") as the runaway-spawn guard; an at-cap call is refused with a clear error naming the cap. User-created agents are never blocked by it. Deliberately no 0 = unlimited escape hatch — the minimum is 1 and out-of-range values are rejected, following the agents.maxConcurrentAdapters precedent. Read live per call, so a settings.update applies to the next ws.agent.create({ topLevel: true }) call), agents.acpNodeMaxOldSpaceMb (number, default 8192, min 1024, max 65536, TOML-backed under [agents] — the V8 --max-old-space-size cap in MB injected via NODE_OPTIONS into Node/Electron ACP provider processes (and npx-fallback spawns, which are always Node); native-runtime providers ignore it. Raises V8's ~1.7 GB default, which V8-OOMed long-lived coordinator sessions mid-turn with no error surfaced (STAB-50). Precedence: an inherited NODE_OPTIONS that already contains --max-old-space-size (the daemon never clobbers a parent cap; the child inherits it untouched) > the INTENTD_ACP_NODE_MAX_OLD_SPACE_MB env var > this setting > the default. Out-of-range values are rejected with -32602 (below 1 GB the cap is smaller than V8's own default). Read live per spawn, so a settings.update applies to the next spawned or respawned provider process — already-running agent processes keep their cap until they restart).

  • Notifications: notifications.enabled, notifications.soundEnabled, notifications.soundOnlyWhenUnfocused, notifications.volume (0..=1). The four notifications.* keys are daemon-owned; every entry is non-secret and reset-able via settings.reset.

  • Workspace API tool output (new in intentd): workspaceApi.maxOutputChars (number, default 100000; 0 = unlimited, otherwise 1000..=10000000 — a non-zero value below 1000 rejects with -32602), workspaceApi.toonOutput (boolean, default true). TOML-backed under a [workspaceApi] config.toml section; they shape the plain success body of the agent-facing MCP workspace_api tool — the oversized-output redirect and TOON encoding described in §5.22.

  • Tool payload replay & retention (intent-hq/intentd#1757): agents.historyReplayToolContentChars (number, default 4000, min 500, max 100000, TOML-backed under [agents] — the per-block character cap applied to each tool_use input and tool_result output in the recovery replay that rebuilds a lost ACP session; longer bodies are middle-truncated with the inline ... [N characters truncated] ... marker. An out-of-range value is rejected with -32602; there is deliberately no 0 = unlimited escape hatch (below 500 a replayed block carries too little of the call to orient the resumed agent). Read live at replay time, so a settings.update applies to the next session rebuild without a daemon restart. The default reproduces the pre-#1757 replay byte-for-byte. Interaction with already-compacted previews (below): a preview stored at cap A replays at the current cap B by re-truncating when B < A and never expanding when B > A — raising the cap cannot recover characters the sweep already deleted), agents.toolPayloadRetentionDays (number, default 0, min 0, max 3650, TOML-backed under [agents] — the age-based retention window for stored tool payloads: when N > 0, the daemon's periodic retention sweep replaces every externalized tool_use input / tool_result output body (the agent_message_payload side-table rows, i.e. bodies over the 4 KiB inline ceiling) whose owning message created_at is older than N days (message age, not tool-call or last-read time) with a replay-shaped preview rendered at the current agents.historyReplayToolContentChars and deletes the full body — irreversibly: setting the value back to 0 stops further compaction but does not restore bodies already pruned; 0 (the default) disables the sweep and keeps full bodies forever (today's behavior exactly). An out-of-range value is rejected with -32602. Read live at each sweep tick, so a settings.update applies on the next tick without a daemon restart; the sweep rides the daemon's stream-retention loop, whose tick interval is derived from events.streamRetentionHours and capped at one hour — with event retention disabled (events.streamRetentionHours = 0) the loop still ticks hourly for this sweep. Compacted previews survive agent.editAndRegenerate (§5.5): its truncation is suffix-only, so the kept prefix retains its ids, seq and side rows untouched and a pruned block stays pruned (flags and replay preview intact) across an edit. Pruning changes only agent.getMessageBlock, which serves the retained slim preview plus the additive inputPruned / outputPruned flags (§5.5); agent.getConversation output is unchanged for pruned and unpruned blocks, and a pruned session rebuilds with the same replay tool-block XML as an unpruned one at the same cap). Both are editable from the desktop FE's Settings → Advanced "Tool Output & Retention" section (intent-hq/cloudlands-fe#2221).

  • Tools: rtk.enabled (boolean, default false) — enables RTK compressed CLI output mode in agent prompts. When true and the rtk binary is detected on the daemon host's PATH, the system-prompt assembly pipeline injects an instruction layer listing RTK-compatible subcommands (filtered exclusion set). The daemon caches detection per run and never blocks prompt assembly; any failure treats rtk as unavailable. The flag is opt-in (default off) and gated behind binary availability, so disabling or removing rtk restores the original prompt behavior.

  • Agent features (new in intentd): agentFeatures.backgroundHooks, agentFeatures.hostExec, agentFeatures.scripts, agentFeatures.terminalAccess, agentFeatures.browserAutomation, agentFeatures.richChatBlocks, agentFeatures.structuredQuestions, agentFeatures.attentionRequests, agentFeatures.stateSnapshot, agentFeatures.prMonitor (v6.1), agentFeatures.taskGraph, agentFeatures.peerAgents (within v7.5), agentFeatures.mcpTools — booleans, TOML-backed under an [agentFeatures] config.toml section. All default true, including the peer-agents surface (ws.agent.create({ topLevel: true }) / ws.agent.retire, §5.5 "Peer agents"); an explicit agentFeatures.peerAgents = false keeps it disabled for new sessions. Per-feature toggles for what agents see and may call: background hooks (ws.hook.*), one-shot host command execution (ws.host.exec), saved scripts (ws.script.*), terminal read access (ws.terminal.*), browser automation (ws.browser.*), rich chat block prompt guidance (mermaid / ws-block / nav-link), structured questions (ws.app.question.ask — additionally top-level-only regardless of this toggle: a sub-agent bridge prunes/denies the binding with the §7 top-level-only redirect error, never the settings error; intent-hq/intentd#1063), attention requests (ws.agent.reportBlocker / ws.agent.requestDiscussion — ws.agent.reportToParent and the rest of ws.agent.* stay un-gated), the per-turn agent state snapshot injection (the current ws.agent.snapshot() => {…} prompt prefix; §5.5 "Per-turn agent state snapshot"), and centralized PR monitoring (ws.pr.monitor / ws.pr.unmonitor / ws.pr.monitors, §5.42 — ws.pr.snapshot stays un-gated, and turning the toggle off also scrubs the "prefer ws.pr.monitor" cross-references from the ws.hook.schedule and ws.pr.snapshot doc entries), and external MCP server tools (ws.mcp.* — listServers / listTools / callTool: the user-configured external MCP servers' tools forwarded to agents, from background hooks and the interactive workspace_api bridge alike; the listServers projection is non-sensitive by construction — env / headers / command never appear; intent-hq/intentd#1483); peerAgents gates exactly independent top-level agent creation and self-retire (ws.agent.create({ topLevel: true }) / ws.agent.retire — the create gate is arg-conditional (only topLevel: true calls are denied when the toggle is off; plain create is never feature-gated), and the top-level path is additionally restricted to foreground top-level callers — sub-agents denied at the dispatch layer like ws.app.question.ask above, background callers at the handler layer; §5.5 "Peer agents"). taskGraph gates the task-graph delegation prompt and tool-description guidance; it does not deny dispatch of task-graph parameters. Unblocked-wake teaching uses the taskGraph value captured when the parent agent session is created, not the effective setting at wake delivery, so flipping the toggle never changes an existing session's wakes. Toggles are captured at agent-session creation (system prompt) and at per-agent MCP bridge creation (tool surface) — never live-read per call — so a change applies to new sessions only unless noted otherwise below; existing sessions keep the surface they were created with. Two deliberate exceptions: hook.schedule also checks agentFeatures.backgroundHooks live in the services layer, so flipping it off denies new hook schedules immediately from all sessions (including pre-flip ones that still advertise ws.hook.*); already-active hooks are unaffected and run to their terminal state/TTL. agentFeatures.mcpTools is the other exception, and it is both captured AND live: the ws.mcp.* surface (tool docs / JS prelude / dispatch) is captured at bridge creation like every other toggle, and the services layer additionally re-reads the toggle live on every forwarded call — so flipping it off denies MCP tool calls immediately from all sessions, including pre-flip ones that still advertise ws.mcp.*. That live server-side gate also honors the mcp.enableUserServers master switch and the per-server disabled state (a server with enabled: false or listed in mcp.disabledServers is refused), so a ws.mcp.callTool only ever reaches a server the user's MCP settings currently allow. agentFeatures.stateSnapshot follows the same captured-at-creation rule as the other toggles (intentd#1273) — it gates the prompt injection only, and the ws.agent.snapshot() MCP tool is never gated and stays callable either way. Each agentFeatures.* definition carries the optional tokenImpact annotation — an approximate, human-readable prompt-token cost of the gated surface (e.g. "~620 tokens/session"; stateSnapshot is per turn: "~50 tokens/turn"; taskGraph adds "+ variable per completion wake"; mcpTools is "~170 tokens/session") — measured chars/4 on the daemon and rendered dimmed next to the toggle by the FE; presence-detected like every additive field.

  • Desktop control agent feature (prepared addition): agentFeatures.desktopControl (boolean, default true, UI label Desktop control, category agentFeatures, TOML-backed under [agentFeatures]) — desktop control API availability for newly created agents, including delegated agents. This addition is not implemented by the current pins. Missing settings and missing keys in older persisted feature snapshots default to true; explicit false survives session/bridge recreation. Capture and persist each new session's effective value; setting changes apply to new sessions only, without live revocation of existing sessions or deletion of remembered consent. The setting controls neither the protocol capability nor user permission: default-on never grants control. When off, omit operational desktop namespace/help exposure and reject acquisition and screenshot/input actions even through forged dispatch; authenticated cleanup and user revoke/Stop remain available. Desktop consent is independent of agentFeatures.structuredQuestions, including for delegates. Disabling agentFeatures.stateSnapshot suppresses automatic injection only; active start/grant/help release reminders and explicit snapshot calls remain available. See Desktop control — Agent feature availability for the precise gates and cleanup exception. Settings UI must explain “Applies to new agent sessions”; use Stop/revoke to interrupt an existing desktop session.

  • PR monitor (v6.1): prMonitor.debounceSeconds (number, default 60, minimum 10) — the quiet window a changed PR must observe before its consolidated wake is delivered — prMonitor.pollSeconds (number, default 60, minimum 10) — the tick cadence of the centralized loop and the active workspace interval; idle checks use a floor of 120/300/600/900 seconds after 15 minutes/1 hour/6 hours/24 hours without content work, while slower configured intervals still apply (§5.42) — prMonitor.hourlyRequestBudget (number, default 1500, minimum 60, maximum 5000; added within v9.12) — the forge calls per hour the loop plans to spend across all monitored PRs in the steady state — and prMonitor.quotaSharePercent (number, default 50, minimum 1, maximum 100; added within v10.2, intent-hq/intentd#1948) — the share of the forge's remaining PR-read quota (from the shared authoritative quota probe, with a 60-second minimum interval; §5.42) the loop may plan to spend before the window resets. Both are cadence cost models, not hard ceilings: each distinct-PR poll is costed at 3 calls, and the effective per-PR interval — the minimum time between two polls of one PR; the actual revisit rounds up to the next pollSeconds tick at or after it — is the largest of the fastest interested workspace’s idle interval (§5.42), the hourly-budget cadence max(pollSeconds, ceil(distinctPRs × 3 × 3600 / hourlyRequestBudget)) (before idle delay, at the defaults up to 8 PRs keep 60s; 10 PRs → 72s, revisited at the 120s tick; 20 PRs → 144s, at the 180s tick) and the quota cadence ceil(distinctPRs × 3 × secondsToReset / allowed) with allowed = floor(remaining × quotaSharePercent / 100) (integer arithmetic; secondsToReset capped at 2h), rounded up to whole pollSeconds ticks — so the monitor slows down ahead of exhaustion, before the shared rate-limit pause has to stop it, and once allowed < 3 (the share cannot cover a single 3-call fetch) it polls nothing until the window resets (§5.42). A host without the remaining / reset signal, or a failed probe, plans on the hourly budget alone. Each tick fetches at most a capped, oldest-first subset of the due PRs (zero while a stretched interval's fetch spacing has not elapsed since the newest poll, so a stale backlog drains within the planned budget), which can defer a due PR further; no request is counted or blocked against either model, and actual spend can differ (the 3-call unit is a single-page planning constant — the folded GraphQL observation spends less, paginated review / thread reads and degraded-path REST fallbacks more; §5.42 "Per-poll forge cost") — genuine quota exhaustion is handled by the shared rate-limit pause (§5.42). TOML-backed under a [prMonitor] config.toml section. All four are read live by the loop (no daemon restart needed); out-of-range values in a hand-edited config are clamped at read time (sub-floor values up to the floor — quotaSharePercent 0, which would plan no polling at all, becomes 1; an over-maximum hourlyRequestBudget down to 5000 and quotaSharePercent down to 100), and settings.update rejects values outside the catalog range.

  • PR monitor owner quota (planned addition, intent-hq/intent#5887): prMonitor.maxPerAgent (integer, default 5, minimum 1, maximum 100) — active PR monitors per owner across repositories and workspaces. TOML-backed under [prMonitor], read live on registration/adoption. Existing active monitors and same-owner re-registration survive a lowered limit. settings.update rejects non-integers and out-of-range values; schema-valid unsigned integer file values clamp to 1–100 at read time (for example, 0 becomes 1 and 101 becomes 100). Negative integers or values above the u32 maximum fail file parsing; a rejected reload retains the last valid settings. Raising the quota uses the existing shared polling and rate-limit controls (§5.42).

  • PR cache (intent-hq/intent#5610): prCache.maxAgeSeconds (number, default 60, minimum 10, maximum 600) — how old a cached PR read may be and still be served without a forge call by github.pulls.get (§5.27, the hover card) and ws.pr.snapshot (§5.7); an older (or absent) entry is refreshed from the forge with one folded read and re-cached. The cache is the daemon's single in-memory PR cache, shared with the §5.42 monitor loop (which refreshes entries on every poll regardless of this age); it does not change the monitor's cadence. The same value governs the daemon's issue cache behind github.issues.get (§5.27, the issue hover card): an issue read younger than this is served without a forge call, an older one is fetched once and re-cached. TOML-backed under a [prCache] config.toml section. Read live per call (no daemon restart needed); out-of-range values in a hand-edited config are clamped at read time (below 10 up to 10, above 600 down to 600), and settings.update rejects values outside the catalog range.

  • Updates: updates.checkOnIdle (boolean, default true) — whether the daemon asks the sitter (by signalling it with SIGUSR2) to check for updates once it has been continuously idle — updates.idleCheckIntervalMinutes (number, default 60, minimum 5, maximum 10080) — the minimum spacing between two idle-triggered checks, also applied from process start (the sitter checks at startup) — and updates.idleGraceSeconds (number, default 120, minimum 10, maximum 86400) — how long the daemon must be continuously idle before it requests a check. TOML-backed under an [updates] config.toml section; sub-floor values in a hand-edited config are clamped up to the floor at read time (like prMonitor.*), and settings.update rejects values outside the catalog range. These knobs only govern when the daemon asks for a check; the download and the staged restart into the new version are owned by the sitter, and the desktop client need not be connected.

Retired queue batching preference. agents.flushQueuedMessages is absent from the settings catalog. Ready queued messages always batch under §5.5 eligibility rules. Existing TOML values ("all", "systemOnly", "off", true and false) are tolerated as legacy data and stripped during legacy-setting cleanup. External config reloads ignore the retired key; settings.update tolerates and ignores writes to it so older clients cannot disable batching or fail an otherwise valid update batch.

Not exposed (FE-only). Pure frontend/display settings are out of intentd scope and arenot served by settings.*: theme.*, fonts.*, ui.*, workspaceList.*, openIn.*, keybindings.*, promoBanners.*, activityLog.presets,model.pickerCollapsedGroups, preferences.spellcheckEnabled, preferences.betaUpdatesEnabled,providers.completedSetup, linear.issueFilter.

// → request — list all BE-owned settings (sensitive values redacted)
{ "jsonrpc":"2.0","id":51,"method":"settings.list" }
// ← response
{ "jsonrpc":"2.0","id":51,"result":{ "settings": [
  { "path":"server.wsApi.port","label":"Backend connection port","description":"TCP port for backend connections over WSS (server.wsApi.port)",
    "category":"server","type":"number","min":1024,"max":65535,"defaultValue":5181,"value":5181 },
  { "path":"sourceControl.github.token","label":"GitHub token","description":"PAT used by octocrab",
    "category":"sourceControl","type":"string","sensitive":true,"value":null } ],
  "revision":7 } }
// → request — read one setting
{ "jsonrpc":"2.0","id":52,"method":"settings.get","params":{ "path":"sourceControl.activeProvider" } }
// ← response
{ "jsonrpc":"2.0","id":52,"result":{ "path":"sourceControl.activeProvider","value":"github",
  "origin":"default",
  "definition":{ "path":"sourceControl.activeProvider","label":"Source-control provider",
    "description":"Active forge implementation","category":"sourceControl","type":"enum",
    "enumValues":["github"],"defaultValue":"github" } } }
// → request — mutate settings (emits settings:changed)
{ "jsonrpc":"2.0","id":53,"method":"settings.update","params":{ "changes":[
  { "path":"server.wsApi.port","value":5182 },
  { "path":"sourceControl.github.tokenSource","value":"gh-cli","reason":"use gh auth token" } ] } }
// ← response
{ "jsonrpc":"2.0","id":53,"result":{ "applied":[
  { "path":"server.wsApi.port","value":5182 },
  { "path":"sourceControl.github.tokenSource","value":"gh-cli" } ],
  "revision":8 } }
// → request — atomically select a model owned by another provider
{ "jsonrpc":"2.0","id":54,"method":"settings.update","params":{ "changes":[
  { "path":"model.defaultProvider","value":"codex" },
  { "path":"model.providerDefaults","value":{
    "claude-code":"opus-4.8",
    "codex":"gpt-5.6-codex"
  } } ] } }
// ← response (settings:changed carries these changes and revision 9)
{ "jsonrpc":"2.0","id":54,"result":{ "applied":[
  { "path":"model.defaultProvider","value":"codex" },
  { "path":"model.providerDefaults","value":{
    "claude-code":"opus-4.8",
    "codex":"gpt-5.6-codex"
  } } ],"revision":9 } }
// → request — reset one setting to its default
{ "jsonrpc":"2.0","id":55,"method":"settings.reset","params":{ "path":"notifications.volume" } }
// ← response
{ "jsonrpc":"2.0","id":55,"result":{ "path":"notifications.volume","value":0.5,"revision":10 } }

WSS port assignment

server.wsApi.port is the backend connection port. The legacy server.port value does not select a listener or supply a fallback, even if both values are 5182. Changing or resetting the legacy value does not change the WSS port. Existing legacy values remain in config and accessible through settings.* for compatibility; new config templates omit that unused entry.

server.wsApi.enabled defaults to false. While it stays disabled, normal daemon startup neither probes nor assigns a TCP port. The first actual WSS start performs the one-time selection only when server.wsApi.port is absent from config and has no startup override. New config templates leave port omitted or commented under [server.wsApi]; an empty TOML value, a string such as "auto", or a malformed config is not an unassigned port.

The existing numeric control and wire fields remain unchanged:

Port state Settings view Listener behavior
No saved value or startup override value: 5181, origin: "default" Try consecutive ports from 5181 through 65535 once, advancing only on address-in-use
Numeric value in config, whether user-written or automatically assigned That number, origin: "file" Bind exactly that port; no fallback
Nonzero INTENTD_TCP_PORT override The override, origin: "flag" Bind exactly that port; override wins over config

The advertised numeric default is a preference before assignment, not proof that a listener is running there. Clients enabling WSS MUST send only the enable change unless the user actually chose a port: echoing the displayed 5181 as a port update deliberately makes it fixed, even on a new installation. After a successful first enable, refresh the port setting to show the saved selection. Use live status and pairing information for the listening port. There is no Automatic/Fixed mode field or separate remembered-port cache.

The daemon must bind every required address and retain those sockets while atomically persisting the selected number to <data_dir>/config.toml. The assignment changes the origin to "file" and preserves unrelated configuration and comments. Assignment and settings-API writes are serialized; a stale settings snapshot or external edit detected by the final on-disk byte check rejects assignment. External editors do not share that lock: a write between the final check and atomic rename can still race, with the last writer winning. This is not a filesystem compare-and-swap guarantee. Persistence failure releases the new listeners and fails the start; no uncommitted port is advertised. Every later restart or disable/enable cycle uses the saved number. Existing numeric configurations, including generated 5181 values from older versions, remain fixed without migration or guessing how the value was chosen.

To choose a different fixed port, update server.wsApi.port numerically. To deliberately request a new selection, disable WSS over the local transport, reset that setting with settings.reset, then re-enable WSS. Alternatively, stop the daemon and remove its port entry from config before restarting. Reset removes the saved assignment and restores value: 5181, origin: "default"; the next WSS start selects and saves a port again. Unlike the absent-means-auto memory-budget setting above, this setting retains its numeric default on reads and reset. Resetting or writing a port pinned by INTENTD_TCP_PORT is rejected with -32602; remove the startup override and restart to return control to config. Explicit INTENTD_TCP_PORT=0 remains the ephemeral-port test mechanism, not a saved assignment or a production automatic mode.

When WSS is enabled at daemon boot, failure to bind or persist the first assignment exits the daemon nonzero with cleanup and an actionable error. The daemon does not report a successful UDS-only startup in that case. A runtime enable failure returns a settings error without terminating an already-running daemon. Free the configured port or deliberately change/reset it; retrying or restarting through sitter never silently changes an assigned port.

Identity and host settings on shared hosts (10.9)

§5.49 keeps settings.* owner-only. Members use its safe host.executionContext read and the existing provider/model/repository read surfaces, not a settings dump. Local client preferences remain local.

Its always-present enabledProviderIds projects the host's existing execution gate: only explicit providers.enabled[id] === false excludes a registered disableable provider; absent entries are enabled, non-disableable providers remain included, and unknown keys add nothing. It is separate from installation, feature/environment gates and authentication/readiness; members never reconstruct it from those surfaces or local settings. Committed map changes, including entry removal or reset, emit the complete sanitized execution-context snapshot. Missing or malformed projections on older hosts use §5.49's conservative member fallback; the owner's existing setup/enablement policy stays unchanged.

Its gitCredentialPolicy projects only the effective managed-helper switch and the fixed setting name sourceControl.github.exposeGitCredentialToChildren for member recovery. Disabled means no daemon-managed child credential injection, not that alternative Git helpers or all Git operations are unavailable. A configured repository account does not establish valid authorization. Missing or rejected Git/AI execution authorization directs members to the connected host's owner, never to member account setup. Relevant helper/settings, repository auth and AI readiness changes emit the sanitized host:execution-context-changed invalidation; clients refresh on every reconnect. Raw settings/account events and all settings writes remain owner-only; see §5.49 for the typed diagnostic.

identity.provider remains an explicit legacy profile selection path. The new identity.select selects a verified collaboration-purpose account explicitly; repository token refresh, account swap or disconnect is never an implicit selection or unlink of an established principal. Existing identity triples are preserved, including when there is no working repository connection. Profile selection changes neither Intent principal ID nor host/workspace authority. Keep generation fencing against an earlier pending refresh/selection.

The desktop labs:multiplayerEnabled preference is not a daemon setting or permission. It gates the experimental collaboration UI, including the tab and direct routes, and cannot grant access or revoke saved memberships when toggled.

Collaborator-facing machine name

sharing.machineName is a host-scoped string setting (category sharing, default ""), persisted under [sharing] in config.toml. The host owner edits it with settings.update and reads it with settings.get / settings.list; the existing administrator service and transport gates apply. Host members and workspace guests cannot read the settings catalog or update/reset this setting.

Updates trim surrounding Unicode whitespace, reject control characters (including surrounding tabs/newlines), and reject more than 100 Unicode scalar values after trimming with -32602. An empty/whitespace-only value or settings.reset { path: "sharing.machineName" } clears the override. Changes take effect without restart and survive daemon restart. This setting changes display naming only: OS hostname / prettyHostname, personal paired-device aliases, stable host IDs, routing metadata and credentials keep their existing meanings.

Collaborators receive only the collaborationName projection through system.status and open-invite invite.inspect / invite.challenge (both host and workspace scopes). The field is always a string or explicit null on supporting daemons: non-empty saved override or null when unset/reset. Older daemons omit it. Clients use the override only for collaboration labels, otherwise retain their existing pretty-hostname/hostname fallback. Connected clients refresh through their existing status polling and reconnect path; null must clear a cached override. There is no new RPC, event or broader guest settings access.

Optional provider access tokens

These additive sensitive settings reuse settings.get, settings.list, settings.update and settings.reset. They are administrator-only, like the other global settings. Member and guest callers cannot read or change them.

Credential kind Setting path Type Storage
Claude setup token providers.claude-code.accessToken string, sensitive SecretStore, account equals path
Codex access token providers.codex.accessToken string, sensitive SecretStore, account equals path

Clients show token controls only when the corresponding providers.catalog row advertises accessToken, and use its settingPath. Unsupported providers and unknown paths return -32602. Save or replace with settings.update({ changes: [{ path, value: token }] }); remove with settings.reset({ path }). Blank/whitespace-only tokens and non-string values (including null) are rejected with -32602; reset is the removal operation.

Reads return value: "********" when configured or value: null when absent, never the token, token prefix or token suffix. Sensitive definition metadata has type: "string", category: "providers", sensitive: true and no default value. The existing placeholder-preserve behavior applies. Update results and settings:changed events redact tokens; reset returns { path, value: null, revision }. As for other sensitive settings, presence reads treat backing-store errors as absent for display and do not attest provider authentication. Tokens are kept out of config.toml and the ordinary settings database. Existing API-key storage and authentication remain available independently.

Token changes apply to subsequent launches. Saving does not check account eligibility, renew a token, install a provider CLI or mutate a running process. Without a saved token, the target machine must be authenticated separately. A supplied token takes precedence in token-aware launch consumers; rejection must surface as authentication failure rather than selecting another account.

Adapter evidence: the reviewed Claude adapter @agentclientprotocol/claude-agent-acp@0.81.1 merges process environment into the SDK query options, including CLAUDE_CODE_OAUTH_TOKEN. Obtain the value with claude setup-token; see Claude authentication. The reviewed @agentclientprotocol/codex-acp@2.1.1 starts codex app-server with its inherited environment and reads the account via account/read. CODEX_ACCESS_TOKEN is consumed by a compatible installed Codex CLI, not by a new ACP authenticate method. OpenAI's access-token documentation limits these tokens to ChatGPT Business and Enterprise workspaces and specifies CLI/app-server environment support. Use the CLI version required by the token creation dialog. These are Codex-scoped workspace access tokens, not arbitrary copied interactive OAuth sessions. Synthetic adapter tests can prove forwarding; only a live eligible account can establish provider acceptance.