SQLite configuration, YAML import/export, per-session options and persisted settings.
Desk stores configuration in SQLite through its Rust configuration API:
~/.config/desk/channels/channels.sqlite
The config_* tables hold projects, groups, sessions, provider resume IDs,
profiles and settings. Channels uses separate tables in the same database.
The public APIs remain separate; callers never write tables directly.On first use, Desk saves the original desk.yml byte-for-byte as
desk.yml.before-sqlite, then imports it transactionally. The old path becomes
a migration marker. Restarting resumes an interrupted import from that backup,
but never overwrites an initialized database with stale YAML. Invalid input
leaves the original unchanged.The header’s configuration editor presents YAML generated from the database.
Saving validates it and checks the revision read by that editor. A concurrent
change produces a conflict instead of losing someone else’s edit. Local readers
share an in-memory snapshot; one revision observer serves their subscriptions.
Authority checks read the committed revision directly. Plain file edits to the
old YAML path no longer change configuration.For explicit export/import:
Import and desk init --force retain a separate backup of the previous
configuration. desk config prints the actual database path. --file retains
the selected configuration’s identity; its database is channels/channels.sqlite
beside that path.The separate FileSnapshot owner still serves the custody current index, node identity and
scheduler state. It shares reads within a process; it does not replace the domain’s
interprocess lock or durable write procedure. Scheduler status uses this view,
while dispatch guards, snapshot export and mutations read fresh state. An active
Stop continues observing executions, but unchanged targets require no state
transaction; changed targets are committed together before any interrupt.Use a different manifest with --file:
DESK_PUBLIC_ORIGIN is a server environment variable, not a manifest setting.
Set it to the exact browser origin for each Desk instance behind an authenticated
reverse proxy, including its scheme and optional port, without a trailing slash:
DESK_PUBLIC_ORIGIN=https://bb-anna.desk.cloud
The proxy must preserve the browser’s Host. Desk does not infer trust from
X-Forwarded-Host or X-Forwarded-Proto. The configured origin adds one public
host to the localhost, 127.0.0.1, and [::1] operator hosts; local
desk custody commands continue to work. Operator POST requests must also carry
the matching Origin and must not be marked cross-site.Without this setting, public-host requests to GitHub App setup, custody execution
(status, Stop, resume, acceptance), and writer-authority recovery return 403.
A server listening on a non-loopback address logs a warning when the variable is
absent. An invalid configured origin returns operator-public-origin-invalid;
GitHub setup reports the same errors with the github-app-setup- prefix.Persist the variable in the deployment’s configuration generator so provisioning
or reseeding does not remove it. Apply it using the deployment’s restart procedure;
cloud bootstrap credentials may need renewal. This setting does not authenticate
users or replace the proxy’s access control.
A project without custody uses ordinary authenticated Channels conversation.
Invited managed agents can post and edit their own messages without genesis,
Assignment or prepare. Their launch supplies identity automatically.To adopt custody, the operator binds the project to its accepted genesis using
the existing custody entry. A file named GENESIS.md, an OKF template or a
self-declared verified field does not enable custody or establish that decision:
Desk retains explicit project/channel adoption and the associated session IDs
in project-custody-bindings.json under the Channels home. Runtime startup,
publication and notification delivery reconcile the current manifest with this
record. Once adopted, removing custody, removing a channel from its list or
losing the session/project association reports custody-binding-lost; it never
silently makes that scope ordinary. A conflicting repository identity reports
custody-binding-conflict. Restore the intended binding to repair these cases.
A missing/corrupt retained record reports custody-bindings-unavailable.
Keep this file and project-custody-bindings-required.json in full-home backups;
they are not part of a Channels conversation-only checkpoint. No disable/reset
command is provided. File-system owners can still alter these files; this is
operational continuity, not isolation between agents sharing OS credentials.Existing explicit bindings are adopted automatically on upgrade. Other channels
remain ordinary without manual classification. Historic unbound context packets
alone do not imply activation. A project binding applies only to its configured
channels and project sessions; it does not impose custody on unrelated projects
sharing a channel. This record has no separate project/channel/thread policy
hierarchy or policy-version workflow. Previously removed pre-upgrade bindings
cannot be reconstructed from unbound notification history.repositoryId is an explicit stable repository identity chosen for the project;
Desk does not derive it from a local path or a mutable remote URL. Keep it the
same across bindings for that repository. genesis selects up to 32 files,
relative to the repository root, without moving the existing OKF. A fragment is
preserved as a navigation hint; the complete file is archived. Each source is
limited to 256 KiB. Linked documents outside this selection are not traversed
unless a selected document belongs to an OKF bundle identified by an ancestor
index with okf_version: "0.2". Within that bundle, Desk follows Markdown links
and declared knowledge relations, retaining exact document and resource versions.
Discovery is bounded to 256 reached nodes, 2,048 edges and 8 MiB; missing targets,
invalid fragments and exhausted limits remain explicit. External URLs remain
source declarations and are not fetched.For a bundle without an ancestor index, select its root explicitly:
knowledgeRoots replaces index-based root discovery with 1–32 unique
repository-relative directory paths. Use . for the repository root; paths
must not contain .., empty segments, backslashes, fragments or queries.
Nested roots are allowed: the most specific root determines each reached
concept’s identity and bundle-relative links. Traversal starts only from
genesis and follows reached links; it does not scan the directories or create
an index. Sources outside the selected roots remain resource leaves. The same
read limits and checkout boundaries apply. Omitting this field preserves index
discovery. Recorded snapshots retain their selected roots; changing or removing
the current selection invalidates authority based on the previous scope.The recipient’s session identifies the project when multiple projects share a
channel. Genesis is read from that session’s checkout, including a linked Git
worktree, after verifying that it belongs to the project’s Git common directory.
An unrelated clone is not silently substituted. Full HEAD, exact source bytes,
content digest and separate HEAD/index/working-file observations are retained;
missing sources and merge conflicts remain explicit. This records the selected
sources, not a complete atomic worktree snapshot.
Historical lookup also checks the recorded local Git directory observation
before attributing current files to that binding. Replacing a repository at the
same path reports unavailable continuity; the old sources remain readable.Existing add/edit project operations accept custody. Omitting it during an edit
preserves the binding; custody: null clears it. Notifications include
desk custody --anchor genesis-<hash>. Reading a context package shows its
recorded genesis and the current binding from the manifest. Reading a genesis ID
directly shows its recorded sources and current files at that recorded binding.
Historical reading requires neither a running server nor an existing manifest.
Frontmatter and source text do not themselves grant operator authority.An assignment can use an exact knowledge-<hash> Objective anchor as its basis.
Desk selects applicable constraints, gates, dependencies and resources through
the declared relations of that objective. A navigation link alone does not make
a document an assignment requirement. The recorded assignment basis and the
observed version in the current checkout appear separately; a changed document
does not silently replace the historical basis.
remote names an existing Git remote alias in this project. Desk resolves that
alias without changing the agent’s checkout or index. Only the configured node
publishes the journal ref. Other nodes retain their outboxes until remote
read-back verifies the records and required Git bundles. The service retries
temporary outages with a capped delay and checks the current publication policy
before sending or pushing. A removed policy leaves local records available.
Published Git history retains earlier shared content.An enabled journal reports pending while preparing source closures and Git
payloads. pendingIds contains durable inbox/outbox records and updates as new
closures enter the queue; an empty list during preparation is not an archival
receipt. New roots are retained individually before preparing later roots;
cached roots use bounded batches. Completed Git captures can be reused across
priority interruptions after verifying their retained bytes. Only remote
read-back changes the journal state to archived.For multiple Desk nodes, initialize each local public identity with
desk custody identity --init. This creates a node key when absent and prints
nodeId, nodeKeyId, and publicKey as JSON. Repeating it preserves the key;
desk custody identity only reads the existing public pin. Private keys stay
with their existing local key owner. This command creates no assignment or
execution and does not register the node at another Desk.On each sender, set journal.transport.writerUrl to the writer’s HTTPS origin.
On the writer, add the sender’s three public fields to
journal.transport.peers, with an explicit publishChannels list. For example:
Peer channel lists must be subsets of the project’s journal.publishChannels.
An empty effective list permits no intake, including payload uploads. The
writer’s existing HTTP backend serves /api/custody/v1/projects/; expose only
that authenticated route through the configured HTTPS proxy. The rest of Desk
retains its local-trust boundary. Literal loopback
HTTP origins are also supported for local connections and tests.Requests use the registered node key to bind the exact body digest, length and
target project/repository/ref/writer. The receiver keeps unfinished bundle uploads
separate for each sender and key. The transport opens a streamed 202 response
with x-desk-custody-receipt: body-required-v1 after local authentication and
admission. That status and header confirm only admission. Durable intake requires
the complete final JSON body with the matching request digest; an admitted error
also arrives in that body. The sender retains its outbox until a fresh remote Git
read-back supplies the record receipt. A missing final body has an unknown intake
outcome and must be reconciled using the same immutable request, not interpreted
as a successful publication.
Node-key trust, source authorship and operator authority remain distinct.writerNodeId is the bootstrap writer. A managed journal can additionally pin
writerAuthority and writerCandidates; its verified transition chain selects
the current writer after a credential-fenced handover. The authority and each
candidate have distinct pinned node keys and HTTPS origins, and candidates
must appear in the transport peers. A UUID change or Git CAS alone does not
authorize a transfer. Git bundles, worktree snapshots and execution checkpoints
have separate coverage and remote receipts; consult the live custody status
before claiming source-machine recovery.A fresh managed-journal reader also needs its own desk custody identity --init
pin registered in the authority’s journal.transport.peers with explicit source
scope. The authority supplies short-lived read credentials after checking that
pin and the project binding. Keep the GitHub App private key on the authority;
placing it on a worker is rejected as custody-writer-app-key-on-worker. Check
desk custody writer status --project ID for the registered authority and current
writer before a cold remote restore. A peer’s public pin alone grants no write
authority or operator role.
The backend reads the machine-local JSON file named by
DESK_CUSTODY_GITHUB_CONFIG. When the variable is absent, the receiver is
disabled. Credentials and receiver configuration stay outside project knowledge.
For example:
The secret file contains the exact shared-secret bytes; a trailing newline is
part of the secret. An environment reference is also supported:
{"kind":"env","name":"DESK_GITHUB_WEBHOOK_SECRET"}. The configuration file
contains the reference, not the secret value. providerInstance must be a
canonical HTTPS origin without a trailing slash; GitHub Enterprise uses its
own explicit origin. The origin is retained in each received record, so
changing configuration does not reassign historical events to another host.Configure the App webhook URL to end in /api/custody/v1/github/webhook on the
receiver’s HTTPS origin. Desk verifies the exact raw request body with HMAC
SHA-256, checks the current installation/repository scope, and durably records
the delivery before responding with 202. Configuration and secret changes take
effect on subsequent authorization checks without a backend restart.
Repository-less events require allowRepositorylessEvents: true on their
explicit installation scope; this does not grant operator authority. App-wide
ping payloads without an installation are currently rejected by this scope rule.Expose this authenticated route through the HTTPS proxy while preserving Desk’s
existing local-trust boundary. The receiver retains a reconciliation obligation
after startup or intake failure. GitHub does not automatically retry failed
webhook deliveries, and accepting a webhook does not prove complete history.
The App integration below recovers recent deliveries; API reconciliation is configured separately. See
GitHub’s failed-delivery behavior.Accepted events asynchronously produce local object observations in the existing
backend. The original source and every object membership are durably saved
before the event’s projection is marked complete. Startup resumes pending work;
one unreadable event remains a visible gap while other events can progress.
desk custody --anchor github-observation-<hash> reads the exact observation and
raw body. desk custody --anchor github-object-<hash> reads recorded history;
the output supplies --after <observation-id> when further pages remain. Object
history is paged by both record count and bytes. Arrival order and timestamps do
not establish current GitHub state, and a projected merge or answer does not
accept the operator’s goal. Authorized outgoing GitHub operations have a
separate durable intention, remote publication receipt and outcome
reconciliation path; an unknown result is not blindly retried. These records
do not themselves establish QA or operator acceptance.
Register a GitHub App with the repository read permissions needed by your
project: Metadata, Contents, Issues, Pull requests, Discussions, Checks and
Commit statuses. Organization Projects requires its own organization permission.
Select the corresponding webhook events, enable the JSON webhook with SSL
verification, and use the receiver URL above. Installation events invalidate
authorization; they never create operator decisions.Use the same machine-local DESK_CUSTODY_GITHUB_CONFIG file for the registration
and allowed installations. Private-key and webhook-secret values stay outside
this file and outside the project manifest:
An environment reference ({"kind":"env","name":"DESK_GITHUB_APP_PEM"})
can replace the key file reference. The key must be an RSA PEM. Permissions are
an explicit local ceiling and must also be granted in GitHub. Omit permissions
your installation does not use, such as organization Projects for a repository-only
binding. Keep metadata: read for the native repository identity lookup.Start with scopes: [] if the installation does not exist yet. With the registered
App configured, desk custody github install prints its installation URL without
accessing the private key. Complete installation in GitHub, start desk serve,
then run desk custody github inspect to obtain actual installation IDs, account
IDs, granted permissions and suspension state. Add the selected IDs to scopes.
Use actual repository database IDs, for example from
gh api repos/OWNER/REPO --jq .id; local permission selection does not grant
access to a repository excluded in GitHub.Inside the project’s custody.github, explicitly select the installation:
Each API subprocess gets a bounded installation credential lease. Tokens and
JWTs stay in backend memory. App mode removes ambient GitHub tokens, debug
variables and configured secret environment references from the gh child.
Key/configuration changes and installation events invalidate active leases;
authorization is checked again after the request before its result is accepted.
The token cache rechecks grants after at most 60 seconds on its next use, even
when the installation webhook was lost. Repository roots use individual token
scopes; a Project root spanning more than GitHub’s 500 repository IDs per token
is rejected explicitly rather than silently dropping repositories.desk custody github status reports authorization observations and delivery
recovery state; use --after <nextCursor> --limit <N> for subsequent delivery
pages. Installation inspection supports --page <nextPage> if a pass reaches
its limit. desk custody github recover durably requests a new scan; its
response means requested, not delivered or processed. Startup, periodic
checks and configuration repair also start recovery automatically. The worker
checks the authenticated App identity and configured webhook URL before reading
delivery attempts or requesting redelivery. It saves its intention before each
POST and reconciles an unknown outcome before another request.GitHub redelivers to its configured endpoint. The ordinary Desk HMAC intake must
accept the real request before the recovery state becomes local-accepted;
JSON returned by the delivery API is never relabeled as an authenticated raw
webhook. Projection, agent notification and operator acceptance remain separate
states. Recent delivery records cannot recover edits outside GitHub’s retention
window: status preserves that historical gap and requests current API reconciliation.The existing user gh adapter remains available with auth: { kind: user }.
Use it explicitly for personal Projects and other sources requiring user
authorization. Existing configurations without an App preserve their prior gh
behavior; when an App is enabled, each project must choose an auth adapter.
Missing or rejected installation credentials never fall back to a user login.Registration, installation and permission grants require the real GitHub account.
Local HTTP/process tests do not qualify a deployed App or public receiver. See
GitHub App installation,
installation tokens,
and failed deliveries.
Set reconcile: true inside a project’s custody.github to enable authenticated
API reads using the existing gh executor. Binding observations locally or
receiving webhooks does not enable these reads. API-only operation needs no
webhook configuration or public endpoint; the configured provider origin controls
GH_HOST, including for Enterprise.Startup resumes saved page cursors. Repository database IDs resolve through the
provider’s REST API to native node IDs; the exact parsed mapping is preserved as
github-api-repository-<hash>. Project node IDs come from the explicit binding.
Independent connection jobs cover repository Issues, PRs and Discussions,
Project fields/items and their discovered child connections. A page is saved
before child jobs and the cursor checkpoint. Partial responses, access failures
and missing saved dependencies remain coverage gaps.desk custody --anchor github-api-page-<hash> reads an exact saved parsed
gh representation and links its discovery page and repository identity source.
desk custody --anchor github-api-repository-<hash> reads that native mapping.
desk custody --anchor github-api-observation-<hash> reads the corresponding
typed graph. The existing backend projects saved API sources automatically,
retries interrupted writes, and wakes the project journal after durable completion.
All three commands work without a running server or current API credentials.
Native object history combines API and webhook observations without electing a
current version from their arrival order. The exact discovery page and REST root
mapping accompany a published API source through the journal to another Desk.
Its CLI shows any verified remote archival receipt separately from current remote
availability, delivery and acceptance.These are parsed API observations, distinct from raw webhook authentication.
The end of a connection and complete membership in an individual page are separate
facts: a final continuation page contains only its own subset. New graphs use
projector v2; reading an archived v1 graph explains its older end-marker meaning
without rewriting its bytes or ID. A full paginated
pass does not reconstruct unavailable historical edits or provide an atomic global
snapshot. Missing jobs can be recovered from saved parents; a lost source requires
a new pass with an explicit historical gap. Access failures retry under the current
opt-in. Revoking that opt-in stops further API work; saved history stays readable.
Ordinary webhook changes request another pass without resetting unfinished cursors;
periodic reconciliation also catches changes missed by webhook delivery.
Project custody configuration binds local observations by native provider identity,
independently of webhook reception. For example, inside a project’s custody:
github grants local context selection. journal.publishGithub separately permits
publishing the exact raw source to the configured project journal; channel consent
does not grant GitHub publication. Each peer needs its own publishGithub subset.
Cross-repository forks and dependency payloads can include additional native
scopes: include all scopes in the publication binding or explicitly set
allowMixedScopeRaw: true in the applicable publication policy. URL slugs and
Issue numbers never grant source scope.journal.githubRepository identifies the journal’s GitHub repository by native
ID. A matching push to the exact journal ref is transport activity and is not
republished as project work. When that native identity is absent, a push-shaped
source on the configured journal ref stays local with
custody-github-journal-origin-unresolved; Desk does not guess from the remote URL.
Journal pushes also skip API invalidation across projects sharing the backend, so
publishing a new observation cannot recursively trigger another API pass. If the
journal origin is unresolved, a matching ref produces that diagnostic and waits
for the periodic pass instead of starting another pass from the push. Other refs
and ordinary Issue changes retain the usual event-driven path.Explicit GitHub object or observation anchors in assignment sources and applicable
knowledge enter the ordinary channel custody package. The notification supplies
desk custody --anchor commands. Historical packages retain their selected
observations when an Issue changes; unrelated observations do not alter the
package. A completed projection wakes the existing journal worker. A second Desk
imports complete verified remote records into its local archive and rebuilds
object indexes without generating a new provider event.The existing Channels runtime also sends relevant local GitHub changes to the
current assignment’s agent. It uses the same configured project binding and
ordinary role/channel/custody reminders; no additional agent tool or MCP setup is
needed. API acquisition still requires reconcile: true; webhook acquisition
uses the separately configured intake. Imported history does not create live
notifications. Exact assignment blocks and their applicable knowledge determine
the watched objects, so a shared message or common repository does not subscribe
every assignment to every change. Submission recovery and source comparison are
described in the Channels protocol.
The optional managed writer requires custody.github.auth.kind: installation,
journal.githubRepository, the authority’s pinned {nodeId, nodeKeyId, publicKey, url} endpoint, and at least one eligible writerCandidates
endpoint. The bootstrap candidate must match writerNodeId and
transport.writerUrl; every candidate must also be a transport peer with the
same pin. The authority owns the App credential boundary and records a signed
generation change only after the previous writer has been fenced and its
accepted work drained. A new writer rechecks the remote chain before issuing
GitHub effects. writerAuthorityGenesis and writerAuthorityRecoveries pin
explicit operator-approved authority recovery history; a remote signature
cannot amend the trust root on its own.Automatic assignments and worktree snapshots require a project execution
binding to a repository-relative JSON policy and its exact SHA-256 content
digest:
execution: policy: knowledge/f-execution-policy.json policyDigest: <sha256 of the exact policy bytes>
The selected file must also be in custody.genesis. The policy controls
assignment grants and start budgets separately from snapshot capture,
publication, retry interval and coverage. A changed policy file needs a new
operator-approved digest; a local edit does not silently widen autonomy.
Snapshots record Git objects, index, working files and supported LFS/submodule
content with explicit omissions and consistency status. Only a confirmed
remote record supports restore after loss of the original node. The current
snapshot policy includes ignored files unless explicitly excluded, so do not
enable remote publication for a worktree containing secrets until the
secret-exclusion and remote access policy in the
cloud boundary design is implemented.The local server still follows the
single-user trust model. A GitHub App installation
authenticates GitHub traffic, not the human using Desk. Cloud deployment needs
the authenticated HTTP/WebSocket and source-level boundary in the linked design
and a fresh independent security/privacy audit.
linear and custom use cells from 1 to 16.A sizes block, written by earlier Desk versions or by hand, is the initial split for a browser that has none for the group; drags are kept by the browser and are not written back.
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.
bypassPermissions applies to codex, claude, opencode, qwen, and kimi; it is a manifest error on grok and pi (which have no per-tool approvals) and silently ignored on bash. Agent profiles are available for codex, claude, and pi. 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.
A profile is a named provider account. Declare profiles once at the top level
and point sessions at them with profileId:
profiles: - id: work-claude provider: claude label: alice@company.com - id: personal-codex provider: codex label: alice@personal.devsettings: deskSkills: falseprojects: - id: product cwd: ~/projects/product deskSkills: true groups: - id: main sessions: - name: api sessionId: api agent: claude profileId: work-claude
id is a lowercase slug; provider is claude, codex, or pi; 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.settings.deskSkills is the default boolean, false when absent. A project or
session can set its own optional deskSkills boolean; omission inherits its
parent. The session override wins over the project override. These settings
apply to supported Codex/Claude launches independently of profileId and do
not rewrite account configuration. Settings → Desk Skills edits the values
in one table and asks about affected running agents only after saving.
See Desk Skills for delivery, hook trust
and next-launch behavior.Desk implements a profile by pointing the CLI at its own directory:
~/.config/desk/profiles/<id>
for CLAUDE_CONFIG_DIR (Claude), CODEX_HOME (Codex), or PI_CODING_AGENT_DIR
(Pi). The directory is
created 0700. Desk also unsets the provider credential variables that CLI
reads for a profiled launch (each provider has its own list), so an ambient
ANTHROPIC_API_KEY cannot silently outrank the profile’s own login.Creating a profile or entering an email as its label does not sign in. Complete
the provider’s login with the selected profile directory, for example:
CODEX_HOME="$HOME/.config/desk/profiles/personal-codex" codex loginCLAUDE_CONFIG_DIR="$HOME/.config/desk/profiles/work-claude" claude auth login
A Codex profile is its own CODEX_HOME, so Codex reads Desk’s hooks.json
from the profile directory and never from ~/.codex. Desk writes it when the
profile is created. For a profile created by an older Desk, run
desk hooks install: it also merges Desk’s hooks into every Codex profile in
the manifest, keeping hooks you added yourself. Codex then asks you to trust the
hooks once in that profile, as it does for ~/.codex; until you do, its
new sessions report codex-hook-untrusted and cannot capture a resume ID.Use codex login status or claude auth status with the same directory variable
to check that profile. Desk lists profile pointers and does not report the
provider’s account status. Credentials remain owned by the provider CLI.
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.
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.
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.
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.
The session form’s Additional CLI command text field stores commandSuffix.
It defaults to an empty string. Desk appends a separating space and the entered
text verbatim to a terminal CLI command, including resumed launches and custom
commands. It does not trim, quote, split, or validate CLI options in this field.
The launching shell interprets the text normally. For example:
- name: assistant sessionId: assistant agent: claude commandSuffix: '--model "sonnet"'
The field supplements the managed launch, preserving its profile, hooks and
resume handling. It is saved for the next launch; saving does not restart a
running CLI. Native SDK sessions do not use terminal command text.Custom commands bypass built-in agent launch logic.
- name: server sessionId: server cwd: ~/projects/product command: npm run dev
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 cover shared subsystem configuration. Per-person interface state (theme, sound,
the host-metrics switch, the motion switch, the notification kinds, sidebar widths, terminal grid splits, open editor
and notes tabs and the autosave mode) is kept by each browser in its local storage and is never
written to desk.yml. Values still present under settings: seed a browser that has none, once.
settings.notifications seeds the Events kinds a browser shows (agent-blocked, channel-mention,
channel-message, agent-error, agent-idle, agent-recovered, agent-exited, each true or
false); a kind switched off is left out of that browser’s Events page from the moment of the switch,
never sounds, and marks nothing on the journal. settings.motion: false
seeds the Motion switch off, which stops every animation in the interface.
settings.showHostMetrics: false seeds the Host metrics switch off.
settings: editor: root: ~/projects/product lsp: enabled: true disabledLanguages: [] agents: enabled: true maxSessions: 4 startupTimeoutMs: 5000 explorerTerminals: - sessionId: term-product cwd: /home/me/projects/product name: product createdAt: 2026-09-14T10:00:00.000Z - sessionId: term-dev-server cwd: /home/me/projects/product name: Dev Server command: npm run dev createdAt: 2026-09-14T10:05:00.000Z
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.
settings.explorerTerminals lists the shells opened with Open in terminal
from the editor’s file tree. Desk writes the rows itself: name is the display
name entered in the create form (the form leaves it blank for the user to
fill in and refuses to submit until it is non-blank; a duplicate name within
the same folder is refused by the server), and
sessionId is minted as term-<name> (slugged, with a numeric suffix on
collision) in the same Moor session-id namespace as agent sessions, so the
manifest is refused at parse time when a terminal id collides with an agent
session id or a row is malformed. command is optional: when present it runs
in cwd every time the terminal starts or boots; when absent Desk starts the
login shell. Terminals are not sessions and never appear in groups or
projects. See Terminals panel.
Desk writes manifest updates through a temporary file and rename, and an open UI follows the manifest on disk: a session or group you add by hand (or through the CLI) appears in the sidebar on its own, without a Refresh.