Desk’s source of truth is a YAML manifest. The default path is:
Most UI actions that create, edit, reorder, resize, or delete workspace objects write back to this file atomically. Use a different manifest with --file:

Top-level shape

The manifest has four top-level blocks:
  • profiles: named provider accounts a session can run under
  • groups: root-level agent groups that are not tied to a project
  • projects: named work roots with their own groups and sessions
  • settings: UI and subsystem state

Projects

Projects give Desk named working roots. Project-scoped sessions inherit project.cwd unless the session overrides cwd.
The editor, Git, GitHub, Projects, notes links, terminal context actions, and channel file links can all jump through these roots.

Groups and layouts

Groups organize sessions in the agents multiplexer.
Supported layout kinds:
  • 1x1
  • 2x2
  • 3x3
  • 4x4
  • linear
  • custom
linear and custom use cells from 1 to 16. Desk also persists split sizes when you drag-resize cells:
The UI writes order on projects, groups, and sessions when you drag-reorder the tree.

Sessions

A session needs a name, a durable sessionId, and either a built-in agent or a custom command. The UI and desk add mint collision-free ids. When you add a session by hand, use a 3-64-character lowercase id that starts with a letter and contains only letters, digits, and dashes.
Built-in agent values:
  • codex
  • claude
  • opencode
  • qwen
  • kimi
  • grok
  • bash
bypassPermissions applies to codex, claude, opencode, qwen, and kimi; it is a manifest error on grok (which has no per-tool approvals) and silently ignored on bash. Agent profiles are available for codex and claude only. See Agent integrations for each agent’s resume-id format, hook file, and credentials. For project sessions, cwd is optional because the project root is inherited. Root-level groups need cwd unless the session uses a command that handles its own directory.

Agent profiles

A profile is a named provider account. Declare profiles once at the top level and point sessions at them with profileId:
id is a lowercase slug; provider is claude or codex; label is what the UI shows. A session’s profileId must name a profile whose provider matches the session’s agent — a mismatch is a manifest error rather than a silent ambient launch, because launching the wrong account is exactly what profiles exist to prevent. Omit profileId to run under whatever the agent CLI is ambiently logged into. Desk implements a profile by pointing the CLI at its own directory:
for CLAUDE_CONFIG_DIR (Claude) or CODEX_HOME (Codex). The directory is created 0700. Desk also unsets inherited provider credential variables for a profiled launch, so an ambient ANTHROPIC_API_KEY cannot silently outrank the profile’s own login.
Two consequences worth knowing before you use profiles.Credentials move. The CLI authenticates into the profile directory, so that path holds real credential files. Back it up with the rest of ~/.config/desk, and treat it as sensitively as ~/.claude.Conversation history is per-profile. Claude Code keeps transcripts inside its config directory, so switching a session’s profile points it at a different history and --resume will not find the old conversation. Start a fresh conversation, or move the transcript, when you change a live session’s profile.

UI mode

uiMode selects how a session renders: native (the agent chat surface) or terminal (the CLI’s own TUI in a terminal cell). An omitted field resolves to terminal for every agent; write uiMode: native on a Codex, Claude, or OpenCode session to open the chat surface. Qwen, Kimi, Grok, bash, and custom-command sessions are terminal-only — declaring uiMode: native on them is a manifest error.
The session edit modal switches a live session between modes; the switch restarts the agent process and carries the captured resume id across, so the conversation continues in the other surface. A session with no captured resume id asks for confirmation first, because switching starts it fresh.

Resume metadata

resume is optional in YAML. Omit it to let Desk start a fresh conversation and capture a resume id when the agent CLI exposes one.
Desk validates known resume id formats before persisting them. Codex, Claude, and Qwen use UUID-like ids; OpenCode uses ses_... ids; Kimi and Grok ids are opaque tokens (letters, digits, ._-). Capturing a fresh resume id never changes sessionId; terminal, channels, and attention state remain keyed to the same Desk session. Desk captures every provider’s initial session automatically, only from the managed child launch. The terminal daemon always requires its private per-launch proof; for Codex and Claude it additionally verifies the provider transcript on disk before it persists the ID; a generation number by itself is not authorization. Resuming a different conversation manually inside the provider TUI creates a provider-session mismatch. Desk records that transition durably and blocks relaunch instead of silently replacing the configured resume. Review the two IDs shown in the continuity banner, then authorize the observed conversation explicitly:
The rebind is compare-and-swap guarded by the daemon’s pending transition. If the command is interrupted after the manifest update, retry the exact command; an already-applied matching transition succeeds without changing authority. Do not substitute a fuzzy provider ID or edit resume by hand. If the old process is gone after a restart, the same command can recover the saved transition once the daemon confirms holder absence and revalidates the exact launch proof, current generation, transcript, and manifest binding. An unadopted live holder or unknown holder state still blocks recovery; --force does not bypass identity checks. Rebind changes the binding, not the running process: restart that session afterward to resume the selected conversation. rebind-provider-session preserves the manually resumed conversation by moving the durable binding to the observed ID. In contrast, reset-provider-session <name-or-sessionId> --force clears the binding and authorizes exactly one fresh provider launch. Reset is the destructive recovery for intentionally starting over, not a replacement for rebind.

Permission bypass

bypassPermissions controls supported agent CLIs:
For Codex and Claude, Desk passes the agent’s dangerous bypass flag when enabled. For OpenCode, Desk sets per-session OPENCODE_CONFIG_CONTENT so permission["*"] is allow when enabled and ask when disabled.

Custom commands

Custom commands bypass built-in agent launch logic.
Desk runs the command under Moor and exposes it through the terminal daemon, but it does not provide agent-specific resume, bypass, or attention hooks unless the command implements them itself. A custom command has no Desk hooks, so its session reports unknown activity — Desk says so rather than inferring a state from its output.

Settings

Settings cover UI state and subsystem configuration:

Editor settings

settings.editor.root is the active workspace root for the editor and Git subsystems. Autosave modes:
  • off: save with keyboard or UI commands
  • after-delay: save after autosaveDelayMs
  • on-focus-change: save when editor focus leaves the file
autosaveDelayMs is clamped by the server to a safe range.

Notes settings

Notes always live in ~/.config/desk/notes. The notes settings block stores open files and the active note; it does not change the notes root.

LSP settings

LSP support is default-off. Enable it explicitly:
Desk detects languages under the active editor root. disabledLanguages is the normal user control for turning off a detected language. Advanced fields such as serverCommands, maxSessions, and startupTimeoutMs override the built-in language server behavior. See IDE and LSP and Agent integrations before changing them.

Atomic writes

Desk writes manifest updates through a temporary file and rename. If you edit the manifest by hand while the UI is open, refresh the UI after saving so later UI edits do not overwrite your manual change.

Next steps