Supported session kinds
Built-inagent values:
codexclaudeopencodeqwenkimigrokpibash
command.
Session identity
Every session has a durablesessionId. Desk preserves it across edits and
uses it for the Moor control socket, terminal state, channels delivery,
attention, and agent events. The agent’s resume id remains a separate
provider-owned value.
Desk sets these environment variables for managed launches:
DESK_SESSION_GENERATION is what lets Desk reject a report from a producer
that outlived a restart: a hook stamps the generation it was launched in, and
the daemon refuses anything carrying an older one. A hook process that inherits
no generation posts nothing — silence is the correct answer when an event
cannot be attributed, and it is why a session started before its hooks existed
reads unknown rather than reporting stale state.
A session bound to an agent profile also
receives that provider’s credential-directory variable — CLAUDE_CONFIG_DIR,
CODEX_HOME, or PI_CODING_AGENT_DIR — pointing at the profile root.
Managed hook and adapter API
desk custody managed --request FILE|- sends a bounded JSON request to the
existing backend’s POST /api/custody/v1/managed. It requires the caller’s own
managed launch credential. An author name, hook session ID, permission mode or
approved field cannot supply identity or authority. Operator lifecycle,
acceptance and the existing GitHub administration routes remain operator-only.
The version 1 envelope is {schemaVersion:1, requestId, kind, ...}. Use
capabilities first and check its version and supported effects. An old,
unavailable or incompatible runtime is a failed mandatory check. The wire
schemas live in src/shared/managedRuntime.ts.
Channels effects still execute through
desk channels post/edit. The native
store checks the effective sender, original edit owner and pinned current
sources at commit. A previous effect.check cannot bypass a later Stop,
source edit or changed launch binding. Managed GitHub intentions retain their
authenticated execution; their existing executor repeats generation, Stop,
source/grant and journal writer checks immediately before the effect. Version
1 refuses managed dispatch on a remote writer rather than assuming that its
source execution remains live.
Successful responses contain schemaVersion, requestId, requestDigest,
kind and result. The digest binds the exact schema-parsed request; it is
not an authorization token. requestId correlates responses. GitHub’s
operationId remains the durable idempotency key. Before execute dispatch, the
CLI durably retains the request and operation ID. If its profile state directory
is unwritable, it uses a private temporary directory and reports that fallback;
if neither is writable, it refuses dispatch. On a lost or refused response it
reports the retained path. Read its outcome before
retrying the same intention; do not invent a new operation ID.
Lifecycle adapters may save this context alongside their private continuation
state, full goal, corrections, approach history and pending request IDs. Those
snapshots are restoration data, not a second assignment or permission ledger.
Reading another managed session’s context/outcome is refused. Finishing a turn
while waiting for a person or external owner does not claim goal acceptance;
an unavailable finish observation must not start an infinite Stop-hook loop.
This API does not intercept arbitrary shell, filesystem or network tools, and
does not control effects performed with independent external credentials.
Provider hook registration, actual invocation, child/resume/compaction delivery
and failure behavior must be verified on each supported host/mode. Desk’s
desk-agent-event hooks report lifecycle observations; their successful exit
alone is not effect authorization.
Codex
Codex sessions launch ascodex, with flags added for the features the session
enables. Desk no longer configures Codex’s terminal notification settings:
state comes from Codex’s own lifecycle hooks, so there is nothing to parse out
of the TUI.
When permission bypass is enabled, Desk adds Codex’s dangerous bypass flag.
When resume is set, Desk launches Codex in resume mode.
When LSP agent access is enabled, Desk passes a desk_lsp MCP server definition to Codex so the agent can call Desk’s language-server tools.
Claude
Claude sessions use Claude Code’s CLI and support:- resume ids
- dangerous permission bypass
- optional MCP config for Desk LSP tools
- hook settings for state reporting and resume capture
--settings at launch:
~/.claude/settings.json. That file is the operator’s —
their model, their hooks, their preferences — and a tool that installs its
reporting by editing it has taken something it was not given. The launch flag
achieves the same thing for the session Desk starts and leaves the operator’s
file exactly as they wrote it. desk hooks install writes Desk’s file.
OpenCode
OpenCode sessions launch through the configured OpenCode binary. Desk resolves it from:DESK_OPENCODE_BINPATH~/.opencode/bin/opencode
OPENCODE_CONFIG_CONTENT:
- bypass enabled:
permission["*"] = "allow" - bypass disabled:
permission["*"] = "ask"
ses_... ids. Desk can discover recent OpenCode sessions from opencode session list --format json and persist a matching id.
Qwen
Qwen sessions launch asqwen (Qwen Code, a Gemini-CLI fork). Desk supports:
- resume ids (
--resume <id>, a UUID) - permission bypass (
--yolo) - hook settings for state reporting and resume capture
desk_lsp for Qwen in two parts. desk hooks install registers
the server once in ~/.qwen/settings.json (Qwen has no per-session MCP flag),
unconditionally — the install command has no view of LSP settings. The
per-session part is gated: only when LSP agent access is enabled does a Desk
launch carry the session’s DESK_LSP_ENV_FILE in the environment. Without
that variable the registered tools stay visible to Qwen but every call
returns a missing-environment error — including in Qwen sessions run outside
Desk. Note that the variable lives in the Qwen process environment, so child
processes Qwen spawns inherit it; the token it points to stays bound to the
launching session’s workspace.
Qwen’s hooks are Claude-compatible and live in:
desk hooks install merges Desk’s hooks into that file and also fills two
terminal-context keys Qwen exposes only through settings —
ui.mouseTracking: false and ui.useTerminalBuffer: false, so a click does
not paste mouse-report bytes and the pane has one scrollback instead of two.
It fills only keys the operator has not set: an explicit operator value is
never overridden, and other settings are untouched. If the file is malformed
JSON it is backed up and skipped rather than overwritten. Qwen reads a
command hook’s timeout in milliseconds (unlike Claude/Codex, which use
seconds), so Desk writes 10000 there.
Permission bypass maps to Qwen’s --yolo (auto-approve all tools; needs Qwen
Code ≥0.21.13 — older CLIs reject the flag, and Qwen’s own auto-updater keeps
the effective version current).
A successful --resume keeps the conversation id. What Qwen does not do
is persist a resumable session before the first message is exchanged, and a
fresh launch mints a new id — so a Qwen pane restarted before any input
carries an id the CLI rejects. Desk keeps the pane alive (see
Resume capture) rather than letting it exit.
Qwen needs a provider credential. Point it at Alibaba ModelStudio, a third-party
key, or any OpenAI-compatible endpoint through Qwen’s own Connect a Provider
flow.
Kimi
Kimi sessions launch askimi (Kimi Code). Desk supports:
- resume ids (
--session <id>; Kimi mintssession_...values, which Desk validates as opaque tokens) - permission bypass (
--yolo) - hook settings for state reporting and resume capture
[[hooks]] blocks appended to:
desk hooks install rewrites only Desk’s own blocks and leaves operator-authored
hooks in place; a config whose hooks key is not an array of tables is skipped
rather than corrupted. Validate with kimi doctor.
Kimi’s coding endpoint needs an active Kimi (Moonshot) membership for OAuth, or
a provider key added through kimi provider.
Pi
Pi sessions launch aspi (pi.dev, the @earendil-works/pi-coding-agent
CLI). Desk supports:
- resume ids (
--session-id <id>; Pi mints UUIDs, and the exact id is opened or created, so a pane restarted before its first message resumes cleanly) - state reporting and resume capture through a Desk-owned Pi extension
desk hooks install writes that extension next to the event shim. It embeds
the same producer runtime as the OpenCode plugin and posts to Desk in-process,
in event order, without spawning anything. It listens to Pi’s session_start
(with its reason, so a started or resumed pane reads idle at once), input
(every submitted prompt, including one Pi queues while it is still streaming,
which is what carries a Channels delivery id back), tool_call and
tool_result, agent_end and agent_settled (a turn ends only when Pi will
not continue on its own; a failed run reports its error class: rate limit,
overloaded, authentication, billing, server error), and session_shutdown.
Nothing is written into ~/.pi/agent.
Pi has no per-tool approval system: every tool call runs without asking, so
there is no permission-bypass flag and the bypass checkbox is hidden for it.
Project trust (.pi/ extensions, settings, skills) is Pi’s own prompt and
appears inside the pane.
Pi uses its own provider logins (/login inside the pane; credentials live in
~/.pi/agent/auth.json). An agent profile
points Pi at ~/.config/desk/profiles/<id> through PI_CODING_AGENT_DIR.
That directory is Pi’s whole agent directory, not only its credentials: a
fresh profile starts with no logins, no settings, and no packages, so install
account-rotation extensions into it explicitly, for example
PI_CODING_AGENT_DIR=~/.config/desk/profiles/<id> pi install npm:pi-multiprovider.
Desk’s own extension is passed with -e and does not depend on that directory.
A profiled launch unsets the provider keys Pi reads from the environment (the
Anthropic, OpenAI, Z.AI, Gemini, Copilot, OpenRouter, xAI, Groq, Mistral,
DeepSeek, Kimi, MiniMax, Qwen and other API-key variables from Pi’s provider
table), so an ambient key cannot outrank the profile’s own login; each
provider’s profile scrubs its own list.
Channels deliveries are pasted into the Pi pane the same way as for Claude
(bracketed paste, settle, Enter) and acknowledged through the extension’s
input report. Pi’s project-trust prompt appears at startup in a folder with
.pi/ resources; a delivery pasted while that prompt is open is not verified
behaviour.
Grok
Grok sessions launch asgrok (superagent-ai’s grok-cli, published to npm as
grok-dev; it runs under Bun). Desk supports:
- resume ids (
--session <id>; Grok mints 12-hex values, which Desk validates as opaque tokens) - hook settings for state reporting and resume capture
SessionStart lazily
on the first prompt (not at launch), so a freshly launched Grok pane reads
unknown until the first message, and its tool hooks fire only for the bash
tool. There is no periodic heartbeat to fill that gap: a long non-bash tool
run (e.g. an MCP tool from Grok’s own settings) outlives the working lease
and the session reads unknown until the next hook fires. Grok cannot join desk_lsp yet: it reads MCP servers only from its
global settings and spawns them with a sanitized environment, so the
per-session token cannot reach the server.
desk hooks install only writes a new agent’s hook config when that CLI’s
config directory already exists, so a machine that never installed Qwen, Kimi,
or Grok is left untouched.Bash
Bash sessions run:Permission bypass
The Add Session modal shows a bypass-permissions option for Codex, Claude, OpenCode, Qwen, and Kimi. The manifest field is:--yolo.
For OpenCode, Desk maps it to a per-session OpenCode permission config. Unchecking the box makes OpenCode ask for tool permissions.
Grok has no bypass flag (it has no per-tool approvals at all): the checkbox is hidden for it, and bypassPermissions: true on a Grok session is a manifest error rather than a silent no-op. (A bash session still ignores the field silently, as before.)
Resume capture
Desk can start a session withoutresume and later capture the conversation id:
- Codex: reads Codex session records and startup shell snapshots
- Claude, Qwen, Kimi, Grok: receive hook events carrying the provider session id
- OpenCode: queries OpenCode’s session list
desk reset-provider-session <sessionId> --force.
State reporting
Every supported CLI reports its own lifecycle to Desk through hooks thatdesk hooks install writes. Each hook posts a small, bounded observation —
which event fired, and the few fields Desk needs from it — to
/api/agent-event. Desk decides what that observation means server-side, so
one definition of “working” serves every provider instead of one per adapter.
The facts a provider can assert:
Anything a provider does not assert stays unknown. Desk never fills the gap
from terminal output: the OSC 9 and BEL sniffing earlier versions used is gone,
along with the screen poller that backed it, because a signal that means
“something happened” cannot be told apart from one that means “a turn ended”.
Attention is a projection of that state for the operator. It does not gate
channel delivery — see the delivery engine.
Hook configuration is read when a session launches. A session started before
desk hooks install ran reports nothing until it is restarted, and shows
unknown in the meantime.Agent LSP access
When LSP is enabled and agent LSP access is enabled, Desk wires thedesk_lsp MCP server into supported managed agents (Codex, Claude, and Qwen).
The server exposes language-server tools such as hover, definitions, references, diagnostics, symbols, completions, rename preparation, rename edits, formatting, and code actions.
The MCP surface is token-bound to the session workspace. Agents receive the token through a locked-down runtime env file or agent-specific MCP config. Tools return data and edits; they do not directly apply changes to files.