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.
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.
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 bymodel.defaultProvider; kept in the catalog during the deprecation window but read-only on the wire —settings.updatetolerates-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 inconfig.tomlunder the[model]table asdefaultReasoningEffort— 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 cachedeffortLevelsprovably does not list is dropped with a daemon warn log rather than rejected (§5.11). Agent model resolution walksmodel.providerDefaults[provider]→model.default(the settings-chain step of the daemon-side creation-time resolver, §5.5 — specialist frontmattermodeltakes precedence over this chain, and every result is provider-guarded). ThequickActions.*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 namedbackgroundAgents.*before that rename, and the old paths are retired — gone from the catalog (settings.listnever advertises them;settings.get/settings.resetyield-32602) but tolerated-and-ignored bysettings.update, while aconfig.tomlstill carrying[backgroundAgents]has its values carried over once at boot — per member (defaultModel/typeOverrides/providerSettingsare applied individually, so one malformed legacy value never discards its valid siblings), into eachquickActions.*key still at its schema default, so an already-migrated or deliberately re-picked value is never clobbered, and a legacy member with noquickActions.*counterpart is dropped with a warning — before the legacy table is stripped. The effective default provider is the dedicatedmodel.defaultProviderkey (intent-hq/intentd#1648): registry-validated and whitespace-trimmed on read, so a stale or mistyped value reads as unset. The deprecatedproviders.activekey is never consulted — a one-time boot migration carries a legacy value intomodel.defaultProvider(only when the target is still unset and the value names a registered provider) and removesproviders.activefromconfig.tomlregardless, 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 ofmodel.providerDefaults/quickActions.typeOverrides) must be bare model ids:settings.updaterejects 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 formermodel.workspaceOverrideskey is retired: it is gone from the catalog (settings.listnever advertises it;settings.get/settings.resetyield-32602), andsettings.updaterejects it as an unknown setting with-32602, including in mixed batches: no entries are applied or published insettings:changed. Boot compatibility remains for old persisted state: any stale SQLite row is deleted, and a legacyconfig.tomlkey 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]asdefault; 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 nothidden, §5.11), and falls back toimplementorwhen the key is unset or does not resolve.specialists.dir(optional string —settings.updateon it always yields-32602(read-only), whilesettings.resetyields-32602only 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 theINTENTD_SPECIALISTS_DIRstartup pin (or theintentd serve --specialists-dirflag, which folds into the env var pre-runtime — the flag wins over an inherited env value), else a hand-written[specialists] dirin 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 FEgit-env consumer),workspace.defaultShell,workspace.autoCommit,workspace.cowIsolation(boolean, defaultfalse— CoW workspaces + per-agent sandboxes:workspace.create/workspace.duplicateprovision the checkout as a standalone CoW clone instead of a linked worktree (§5.1), andagent.delegatedefaultsisolationto"cow"when the param is omitted (§5.5); ignored by cache-hydrated creation and byworkspace.duplicateof a standalone-checkout source, which are always standalone (§5.1); consulted only at provisioning time — the resultingcheckoutModeis immutable per workspace (§5.1); requires CoW filesystem support on the workspaces root — the FE gates the toggle onWorkspace.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 appends127.0.0.1to the effective bind set unless the configured set already carries127.0.0.1or 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's127.0.0.1forward; the persisted value is never rewritten (settings.getreturns 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'stypeas"string"for FE back-compat, butsettings.get/settings.listreturn 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 toserver.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, defaultfalse, 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 asettings.updatebatch the tunnel toggle applies afterserver.wsApi.enabled(a batch enabling both starts the listener first). The sidecar's stabletc…address — derived from a key persisted in the daemon data dir, so it survives daemon and sidecar restarts — ridespairing.getInfo/server.pairingInfo/system.statusas the additivetcAddressfield and thetc=pairing-URI param (intent-hq/intentd#1623; §5 fast-path catalog). The tailcat tunnel is unrelated to the/tunnelloopback port-forwarding endpoint, §1.4. Live-applied: enabling starts the sidecar as part of thesettings.update(a sidecar that fails to start fails the update with-32603), disabling stops it; disablingserver.wsApi.enabledalso stops a running tunnel, since it forwards to that listener — without touching the persistedserver.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, defaultfalse, 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 theserver.bindAddresshook). Self-termination guard: enabling it from a direct TCP (WSS) connection is rejected with-32602before 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 theserver.wsApi.enableddisable 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, default256, min0, max100000, 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) and0disables 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 byserver.wsApi.enabled(the formerserver.listenModekey 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 onlygithub),sourceControl.github.tokenSource(auto|env|gh-cli|explicit; defaultauto— secrets store → env →ghCLI),sourceControl.github.token(sensitive),sourceControl.github.apiBaseUrl(GitHub Enterprise support),sourceControl.github.exposeGitCredentialToChildren(boolean, defaulttrue— 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 rawGITHUB_TOKEN/GH_TOKEN). Per-provider config is namespaced assourceControl.<provider>.*so future hosts slot in assourceControl.gitlab.*,sourceControl.bitbucket.*, etc. (replaces any flatgithub.*keys). GitLab (v10.5, §5.27 "Provider-generic auth —sourceControl.*"):sourceControl.gitlab.host(string, defaultgitlab.com— the bound instance as a barehost[:port], no scheme; written by a successfulsourceControl.connect { provider: "gitlab" }and read as the defaulthostof every gitlabsourceControl.*call),sourceControl.gitlab.oauthClientId(string, default""— the public OAuth application id used for the device authorization grant againstsourceControl.gitlab.host; empty ⇒deviceGrantSupported: falseunless the host isgitlab.comand 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 reportedhost),sourceControl.gitlab.token(sensitive — the GitLab credential the device grant or PAT connect persists to the secret store under accountsourceControl.gitlab.token, the first slot of the GitLab resolution chain ahead of theGITLAB_TOKENenv fallback;sourceControl.revoke { provider: "gitlab" }deletes it). -
Identity (v10.8, §5.48 "Identity model"):
identity.provider(enumgithub|gitlab, nullable; defaultnull) — which connected forge is the primary principal's identity.null⇒ implied: the one connected forge, or, with both connected, the current identity (elsegithub; the FE prompts the user to choose). Writing a value is an explicit re-key of the primary principal's identity triple (GET /useron GitHub,GET /api/v4/useron GitLab) that publishesprincipal: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 theirpinProvider/pinHostto the new one. Non-secret;settings:changedfires 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 accountlinear.token, the exact entry thelinear.*namespace's secret-store-firstautotoken resolution reads (§5.28), sosettings.updateon this path is the FE "connect Linear" flow. -
Sentry account (new in intentd):
accounts.sentry.token(sensitive) — the Sentry API tokenused by thesentry.*namespace (§5.29);accounts.sentry.organization(string) — the Sentryorganization slug (non-secret companion). -
Voice (new in intentd):
voice.provider(enum:elevenlabs|openai, defaultelevenlabs) — the transcription providervoice.transcribeuses when the call carries no per-callprovideroverride (§5.41);voice.language(string, optional — no default) — the default transcription language hint (ISO-639-1 code, e.g."en") applied when avoice.transcribecall carries no per-calllanguage(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, likevoice.provider; unset or blank means provider auto-detection);voice.openai.model(enum:gpt-4o-transcribe|gpt-4o-mini-transcribe|whisper-1, defaultgpt-4o-transcribe) — the transcription model the OpenAI provider posts (§5.41 "Providers"; TOML-backed under the[voice]section of config.toml, likevoice.provider);voice.vocabulary(object, non-sensitive — a JSON string array; default =["Intent"], see §5.41 "Context mapping") — the user-editable vocabulary biased into everyvoice.transcribecall, read per call and merged ahead ofcontext.keyterms(§5.41; SQLite-backed like the other opaque bags — noorigin; 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, default50, min0, max100; v5.1) — the cap on the auto-derived workspace vocabulary injected intovoice.transcribecalls carrying aworkspaceIdand served byvoice.getWorkspaceVocabulary(§5.41 "Workspace vocabulary"; TOML-backed under the[voice]section of config.toml, likevoice.provider;0disables workspace vocabulary entirely — no derivation, no injection; a change takes effect on the next derivation);voice.elevenlabs.apiKey(sensitive) andvoice.openai.apiKey(sensitive) — the provider API keys, persisted to the daemon's file-backed secret store (~/intent/.secrets.json,0600) likelinear.token. Key resolution is secret store first, then theELEVENLABS_API_KEY/OPENAI_API_KEYenvironment variable fallback; the keys are never logged, echoed, or returned over the wire (redacted insettings.list/settings.getlike 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, default6, min1, max64, TOML-backed under[agents]— the daemon-wide cap on concurrently live ephemeral ACP adapters: the one-shotagent.completeOncecompletions (§5.32) and model probes (§5.30) that spawn a provider-CLI chain without holding anagents.maxConcurrentslot. Over-limit calls queue instead of spawning and are rejected with-32603adapter-busy(§9) if their owntimeoutMsexpires while queued. Deliberately no0= unlimited escape hatch, unlikeserver.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, default20, min1, 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 })— originallyws.agent.spawnPeer, merged intocreatewithin 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 no0= unlimited escape hatch — the minimum is 1 and out-of-range values are rejected, following theagents.maxConcurrentAdaptersprecedent. Read live per call, so asettings.updateapplies to the nextws.agent.create({ topLevel: true })call),agents.acpNodeMaxOldSpaceMb(number, default8192, min1024, max65536, TOML-backed under[agents]— the V8--max-old-space-sizecap in MB injected viaNODE_OPTIONSinto 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 inheritedNODE_OPTIONSthat already contains--max-old-space-size(the daemon never clobbers a parent cap; the child inherits it untouched) > theINTENTD_ACP_NODE_MAX_OLD_SPACE_MBenv 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 asettings.updateapplies 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 fournotifications.*keys are daemon-owned; every entry is non-secret and reset-able viasettings.reset. -
Workspace API tool output (new in intentd):
workspaceApi.maxOutputChars(number, default100000;0= unlimited, otherwise1000..=10000000— a non-zero value below 1000 rejects with-32602),workspaceApi.toonOutput(boolean, defaulttrue). TOML-backed under a[workspaceApi]config.toml section; they shape the plain success body of the agent-facing MCPworkspace_apitool — the oversized-output redirect and TOON encoding described in §5.22. -
Tool payload replay & retention (intent-hq/intentd#1757):
agents.historyReplayToolContentChars(number, default4000, min500, max100000, TOML-backed under[agents]— the per-block character cap applied to eachtool_useinput andtool_resultoutput 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 no0= 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 asettings.updateapplies 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 whenB < Aand never expanding whenB > A— raising the cap cannot recover characters the sweep already deleted),agents.toolPayloadRetentionDays(number, default0, min0, max3650, TOML-backed under[agents]— the age-based retention window for stored tool payloads: whenN > 0, the daemon's periodic retention sweep replaces every externalizedtool_useinput /tool_resultoutput body (theagent_message_payloadside-table rows, i.e. bodies over the 4 KiB inline ceiling) whose owning messagecreated_atis older thanNdays (message age, not tool-call or last-read time) with a replay-shaped preview rendered at the currentagents.historyReplayToolContentCharsand deletes the full body — irreversibly: setting the value back to0stops 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 asettings.updateapplies on the next tick without a daemon restart; the sweep rides the daemon's stream-retention loop, whose tick interval is derived fromevents.streamRetentionHoursand capped at one hour — with event retention disabled (events.streamRetentionHours = 0) the loop still ticks hourly for this sweep. Compacted previews surviveagent.editAndRegenerate(§5.5): its truncation is suffix-only, so the kept prefix retains its ids,seqand side rows untouched and a pruned block stays pruned (flags and replay preview intact) across an edit. Pruning changes onlyagent.getMessageBlock, which serves the retained slim preview plus the additiveinputPruned/outputPrunedflags (§5.5);agent.getConversationoutput 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, defaultfalse) — enables RTK compressed CLI output mode in agent prompts. When true and thertkbinary 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 treatsrtkas unavailable. The flag is opt-in (default off) and gated behind binary availability, so disabling or removingrtkrestores 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 defaulttrue, including the peer-agents surface (ws.agent.create({ topLevel: true })/ws.agent.retire, §5.5 "Peer agents"); an explicitagentFeatures.peerAgents = falsekeeps 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.reportToParentand the rest ofws.agent.*stay un-gated), the per-turn agent state snapshot injection (thecurrent 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.snapshotstays un-gated, and turning the toggle off also scrubs the "preferws.pr.monitor" cross-references from thews.hook.scheduleandws.pr.snapshotdoc 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 interactiveworkspace_apibridge alike; thelistServersprojection is non-sensitive by construction —env/headers/commandnever appear; intent-hq/intentd#1483);peerAgentsgates exactly independent top-level agent creation and self-retire (ws.agent.create({ topLevel: true })/ws.agent.retire— thecreategate is arg-conditional (onlytopLevel: truecalls are denied when the toggle is off; plaincreateis never feature-gated), and the top-level path is additionally restricted to foreground top-level callers — sub-agents denied at the dispatch layer likews.app.question.askabove, background callers at the handler layer; §5.5 "Peer agents").taskGraphgates the task-graph delegation prompt and tool-description guidance; it does not deny dispatch of task-graph parameters. Unblocked-wake teaching uses thetaskGraphvalue 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.schedulealso checksagentFeatures.backgroundHookslive in the services layer, so flipping it off denies new hook schedules immediately from all sessions (including pre-flip ones that still advertisews.hook.*); already-active hooks are unaffected and run to their terminal state/TTL.agentFeatures.mcpToolsis the other exception, and it is both captured AND live: thews.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 advertisews.mcp.*. That live server-side gate also honors themcp.enableUserServersmaster switch and the per-server disabled state (a server withenabled: falseor listed inmcp.disabledServersis refused), so aws.mcp.callToolonly ever reaches a server the user's MCP settings currently allow.agentFeatures.stateSnapshotfollows the same captured-at-creation rule as the other toggles (intentd#1273) — it gates the prompt injection only, and thews.agent.snapshot()MCP tool is never gated and stays callable either way. EachagentFeatures.*definition carries the optionaltokenImpactannotation — an approximate, human-readable prompt-token cost of the gated surface (e.g."~620 tokens/session";stateSnapshotis per turn:"~50 tokens/turn";taskGraphadds"+ variable per completion wake";mcpToolsis"~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, defaulttrue, UI label Desktop control, categoryagentFeatures, 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 ofagentFeatures.structuredQuestions, including for delegates. DisablingagentFeatures.stateSnapshotsuppresses 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, default60, minimum10) — the quiet window a changed PR must observe before its consolidated wake is delivered —prMonitor.pollSeconds(number, default60, minimum10) — 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, default1500, minimum60, maximum5000; added within v9.12) — the forge calls per hour the loop plans to spend across all monitored PRs in the steady state — andprMonitor.quotaSharePercent(number, default50, minimum1, maximum100; 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 nextpollSecondstick at or after it — is the largest of the fastest interested workspace’s idle interval (§5.42), the hourly-budget cadencemax(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 cadenceceil(distinctPRs × 3 × secondsToReset / allowed)withallowed = floor(remaining × quotaSharePercent / 100)(integer arithmetic;secondsToResetcapped at 2h), rounded up to wholepollSecondsticks — so the monitor slows down ahead of exhaustion, before the shared rate-limit pause has to stop it, and onceallowed < 3(the share cannot cover a single 3-call fetch) it polls nothing until the window resets (§5.42). A host without theremaining/ 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 —quotaSharePercent0, which would plan no polling at all, becomes1; an over-maximumhourlyRequestBudgetdown to 5000 andquotaSharePercentdown to 100), andsettings.updaterejects values outside the catalog range. -
PR monitor owner quota (planned addition, intent-hq/intent#5887):
prMonitor.maxPerAgent(integer, default5, minimum1, maximum100) — 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.updaterejects non-integers and out-of-range values; schema-valid unsigned integer file values clamp to 1–100 at read time (for example,0becomes1and101becomes100). Negative integers or values above theu32maximum 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, default60, minimum10, maximum600) — how old a cached PR read may be and still be served without a forge call bygithub.pulls.get(§5.27, the hover card) andws.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 behindgithub.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 (below10up to10, above600down to600), andsettings.updaterejects values outside the catalog range. -
Updates:
updates.checkOnIdle(boolean, defaulttrue) — whether the daemon asks the sitter (by signalling it withSIGUSR2) to check for updates once it has been continuously idle —updates.idleCheckIntervalMinutes(number, default60, minimum5, maximum10080) — the minimum spacing between two idle-triggered checks, also applied from process start (the sitter checks at startup) — andupdates.idleGraceSeconds(number, default120, minimum10, maximum86400) — 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 (likeprMonitor.*), andsettings.updaterejects 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 } }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.
§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.
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.
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.