Skip to main content
API reference sections

Schema

GlobalSettings

Everything that applies to Koru as a whole, whoever is consuming it.

Read tolerantly; written strictly. A section is what the settings authority answers - a client reads it - so it ignores a field it does not know: an older Koru reading the section from a newer service keeps every value it understands instead of failing the whole read and falling back to defaults. Nothing ever writes a section: a write is a [GlobalPatch], which still refuses an unknown field, so the service answers a typo with a 400 rather than storing nothing. A field added here is also #[serde(default)], so a newer client reads an older service. (The user's call, 2026-11-03: this reader shipped before any new Global field did. koru-ai D79's chat_font was the first to wait, and landed once the user ruled, 2026-11-04, that an older Koru may ignore it.)

Type object

Shape

FieldTypeDescription
themerequiredThemeChoice

theme= in preferences.conf, and the terminal row's theme=.

Matches koru_draw::theme::ThemePreference: case-sensitive, because that is what the existing parser does and a file written in another case has never been produced by any build.

icon_packIconPackChoice or null

None follows the build's default artwork, now and later; it is persisted as no key, like a font role that follows the app.

Default null

caret_colorrequiredCaretColorChoice

caret_color=, and the terminal row's cursor_color=.

caret_widthrequiredCaretWidthChoice

caret_width=.

x4/x8 are accepted on read and never written - an asymmetry inherited from koru_draw::theme::CaretWidth::parse, kept because files carrying them exist.

caret_heightrequiredCaretHeightChoice

caret_height=.

ui_fontrequiredFontSetting

What a font role is set to.

"Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either.

body_fontrequiredFontSetting

What a font role is set to.

"Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either.

headings_fontrequiredFontSetting

What a font role is set to.

"Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either.

code_fontrequiredFontSetting

What a font role is set to.

"Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either.

chat_fontFontSetting

The typeface the AI chat is set in - its messages and the box a person types in. follow_default follows the app font (ui_font), now and later, rather than a build default of its own: a person who never chooses sees the chat exactly as before, and a later change to the app font carries the chat with it. Persisted as no key while it follows, like every font role. #[serde(default)] so a section from a build without it reads as following the app font.

Default "follow_default"

document_text_sizeDocTextSizeSetting

The document body size. Follows the build's default until chosen.

Default "follow_default"

base_color_lightRgbColor or null

The window's base color - the frame around the panes - for the light theme, as #rrggbb. null follows the theme, now and later, and is persisted as no key, like a font role that follows the app.

Default null

base_color_darkRgbColor or null

The same for the dark theme. Each theme keeps its own; neither is derived from the other.

Default null

accent_color_lightRgbColor or null

The accent - the colour a primary action wears - for the light theme, as #rrggbb; null follows the theme (2026-11-10).

Default null

accent_color_darkRgbColor or null

The same for the dark theme, kept apart for the base colour's reason.

Default null

densityrequiredDensityChoice

density=. Case-sensitive, matching koru_draw::theme::Density.

scroll_speedScrollSpeedChoice

Absent from a file written before it existed, which reads as the default.

Default "2x"

zoomrequiredZoom

A zoom factor, valid only within the range the app has always accepted.

Out of range is ignored rather than clamped - the guarded-geometry stance: a file carrying nonsense keeps the default rather than snapping to a surprising value the person never chose.

default_view_moderequiredViewModeChoice

view_mode= - the default view mode for a newly opened document.

Narrow until somebody chooses (the user's call, 2026-10-31): a readable column is what a new page should look like. Unknown input becomes the default too.

default_canvas_backgroundrequiredCanvasBackground

The canvas background a new document opens with.

Auto persists as the literal auto; a color persists as #rrggbb in lowercase. Alpha is not stored and is always opaque, exactly as today.

pinned_show_titlerequiredboolean
tabs_per_workspaceboolean

Workspace tabs: each workspace keeps its own tabs (true, Per workspace) or the open tabs stay as you switch (false, Shared, the default). Absent from an older file, which reads as Shared.

Default false

popout_closerequiredPopoutCloseChoice

popout_close=. Note RestoreTab persists as restore.

emoji_skin_tonerequiredstring
dictation_gesturerequiredDictationGesture

dictation_toggle= as a named gesture rather than a bare flag.

The disk spelling and the API spelling are deliberately different. On disk it is 0/1, because that is the byte already written in every preferences.conf and three parsers read it. Over the API it is push_to_talk/toggle, because "dictation_gesture": "1" is a riddle at the call site - and the two parsers that read dictation_toggle disagree about true (one accepts it, one does not), which is exactly the kind of question a typed value should stop anybody having to ask.

dictation_languagerequiredLanguageChoice

A language preference: automatic, or one BCP 47 tag.

The authority validates the shape of a tag and never resolves it: the closed set of languages lives with each consumer (koru-i18n for the UI, the speech engine for dictation, the spell engine for spelling), and pinning a copy of any of them here would be a second list to keep true. An unrecognized tag resolves to automatic at the consumer.

Grew out of the dictation preference when spell checking needed exactly the same shape; [DictationLanguage] remains as an alias so nothing renames on the wire or at a call site.

dictation_tierrequiredDictationTier

dictation_quality=. koru_voice::ModelTier's two tiers.

dictation_inputrequiredDictationInput

Which microphone dictation opens.

The device name roams with the person rather than staying on the machine - ADR-252's recorded reasoning, kept deliberately.

spellcheck_enabledrequiredboolean
spellcheck_languagerequiredLanguageChoice

A language preference: automatic, or one BCP 47 tag.

The authority validates the shape of a tag and never resolves it: the closed set of languages lives with each consumer (koru-i18n for the UI, the speech engine for dictation, the spell engine for spelling), and pinning a copy of any of them here would be a second list to keep true. An unrecognized tag resolves to automatic at the consumer.

Grew out of the dictation preference when spell checking needed exactly the same shape; [DictationLanguage] remains as an alias so nothing renames on the wire or at a call site.

favorite_modelsarray of FavoriteModel

The models starred in the chat's model picker, by agent key and model id, in the order they were starred; each pair once. Roaming, like the rest of how Koru looks to this person: a favorite made on one computer is a favorite on the next. An agent turned off on a machine keeps its favorites; the picker hides them there. #[serde(default)] so a section from a build without it still reads.

Default []

close_behaviorrequiredCloseBehaviorChoice

close_behavior in desktop-v1.json, whose variants are kebab-case.

global_shortcutrequiredGlobalShortcut

The chord that raises global search.

Currently writerless - no surface changes it - but the registry defines it anyway, because a setting the app reads and nothing can write is exactly the hole this authority exists to close.

global_dictation_enabledboolean or null

Whether Agent may open a global microphone session and insert into an external application. Off until the person accepts the Voice notice. None means the first-use notice has never been accepted; Some remembers onboarding independently of the current enabled value.

global_dictation_shortcutrequiredDictationShortcut

The person's preferred global Dictation chord. Agent reports the registered fallback separately; this is intent, not availability.

start_on_loginrequiredboolean
reminder_start_on_login_explainedrequiredboolean
automatic_update_checksrequiredboolean
delegate_temporary_rootstring or null

Where Koru makes a folder for a delegated run that belongs to no project (ADR-365). None means the platform's temporary directory - %TMP% on Windows, /tmp on Unix - which is [crate::paths::delegate_temporary_base]'s answer and the only place the default is spelled.

Device-local because it names a directory on this machine: the same person's other computer has different disks, and a path that travelled would send a run to a folder that is not there.

A string rather than a PathBuf because it crosses JSON and the wire, and because an unreadable value has to survive a round trip rather than fail a load - the consumer falls back to the default instead.

recordingrequiredRecordingPrefs

The internal session recorder's prefill. Device-local - see the type.

disabled_agentsarray of string

The coding agents turned off on this machine, by agent key (koru_session_profiles::ProfileLabel::agent_key: claude, codex, …), sorted and without repeats. An agent named here is hidden from the chat's agent picker, the terminal's Agents › menu and every new session choice; its chats and sessions are kept and keep working.

The off list, not the on list, so empty is the default - every agent on - and an agent a later Koru adds is on until someone turns it off. Device-local because the agents installed differ per machine. #[serde(default)] so a section from a build without it still reads.

Default []

default_permissionAgentPermission

The permission a new chat with a supported agent starts in - an agent that has an auto mode (Claude, Codex). Manual by default. The chat's own mode is still the chat's: a change inside a chat applies to that chat only, and this names only where the next new chat starts. Device-local, beside disabled_agents.

Default "manual"

default_permission_otherAgentPermission

The same for every other agent - one with no auto mode (OpenCode): Manual or Yolo, never Auto. Manual by default.

Default "manual"

default_agentstring or null

The agent a new chat starts with, by agent key, or None for Auto - the last agent used, which is what a chat did before this existed. An agent that is turned off or not installed falls back to Auto where a chat is made; the key is kept, so turning it back on brings the choice back.

Default null

agent_orderarray of string

The order the coding agents are listed in, by agent key, as the person dragged them on Agent providers: the chat's agent rail, the terminal's Agents › menu, the Default agent chips and the delegate dialog all follow it. Empty is the built-in order (Claude first); an agent it does not name - one a later Koru adds - follows the named ones in the built-in order. Each key once.

Default []

Example
{
  "theme": "system",
  "icon_pack": "phosphor",
  "caret_color": "default",
  "caret_width": "thin",
  "caret_height": "short",
  "ui_font": "follow_default",
  "body_font": "follow_default",
  "headings_font": "follow_default",
  "code_font": "follow_default",
  "chat_font": "follow_default",
  "document_text_size": "follow_default",
  "base_color_light": "string",
  "base_color_dark": "string",
  "accent_color_light": "string",
  "accent_color_dark": "string",
  "density": "normal",
  "scroll_speed": "2x",
  "zoom": 0,
  "default_view_mode": "page",
  "default_canvas_background": "auto",
  "pinned_show_title": false,
  "tabs_per_workspace": false,
  "popout_close": "ask",
  "emoji_skin_tone": "string",
  "dictation_gesture": "push_to_talk",
  "dictation_language": "automatic",
  "dictation_tier": "compact",
  "dictation_input": "system_default",
  "spellcheck_enabled": false,
  "spellcheck_language": "automatic",
  "favorite_models": [],
  "close_behavior": "ask",
  "global_shortcut": "string",
  "global_dictation_enabled": false,
  "global_dictation_shortcut": {},
  "start_on_login": false,
  "reminder_start_on_login_explained": false,
  "automatic_update_checks": false,
  "delegate_temporary_root": "string",
  "recording": {
    "preset": "",
    "custom_width": 0,
    "custom_height": 0,
    "frame_rate": 0,
    "cursor": false,
    "folder": "string"
  },
  "disabled_agents": [],
  "default_permission": "manual",
  "default_permission_other": "manual",
  "default_agent": "string",
  "agent_order": []
}

The contract has no example values, so example bodies are generated from each schema: every field is shown with a placeholder of its type.

Used by

Generated from contract version 1. Last updated .

Privacy choices

Choose which optional tools Koru may use on this site. Your choice is saved in a cookie in this browser for 12 months. Privacy policy

Privacy choices

Keep you signed in, protect forms and remember your language and this choice.

Always on

PostHog and Google Analytics 4 measure page views and actions such as downloads, using cookies or local storage.

PostHog records how pages are used, with form inputs masked and private pages excluded. Needs Analytics.

Ahrefs Web Analytics measures visits and where they come from, and Google Ads measures whether our ads led to a visit or download.