Multi-Tenant Desktop — Koder Linux Workspaces

Architectural direction for layering a workspace abstraction over the single-user Koder Linux desktop, so a single installed system can host multiple isolated user contexts (work / personal / per-client) switchable from the kolide-shell panel — without falling back to the much heavier "second Linux user account + relogin" pattern. Each workspace owns its own per-user storage tree, Koder ID binding, apps catalogue, and kolide-shell settings. Apps stay multi-tenant by default (`policies/multi-tenant-by-default.kmd`) so this RFC is the desktop-side surface that finally makes the policy visible to the end user.

RFC — Multi-Tenant Desktop (Koder Linux Workspaces)

Version: 0.1.0 — Draft Status: Proposed (20260518) Owner: Koder Linux WG (infra/linux/distro + infra/linux/kolide) Discussion ticket: infra/linux/distro #019

*cope.*This RFC governs only the desktop notion of "multi- tenant" — a single OS user (the live install's koder account) housing N isolated workspace contexts switchable at runtime. Multi- OS-user (two Linux accounts on the same install) is what Debian already does and stays out of scope.

Servers, Flow tenants, and any backend isolation are governed by specs/multi-tenancy/contract.kmd. This RFC reads that spec as a hard upstream contract and only adds the desktop-shell affordances.


D1 — Why now

Three forcing functions converged:

  1. policies/multi-tenant-by-default.kmd ratified that every Koder

    schemaAPIstorage carries koder_user_id from commit 1. Apps on the desktop already plumb this through (e.g. products/horizontal /dek slice 1+2 of #100). What's missing is the user-facing handle: today the user sees one global state and has to trust that the storage layer separates per-user — but they have no visible way to switch to another identity without logging out of the whole desktop.

  2. infra/linux/kolide #008 shipped session-wide Koder ID OAuth

    (libsecretbacked). The tokenstore schema (dev.koder.kolide .token) is keyed by {component, user_id} — already tenancy-aware, but only stores one entry. Trivial to extend to N.

  3. Field requests: lawyers + consultants who service multiple

    clients want one device with strict per-client isolation, not N devices and not N Linux accounts.

D2 — Definition

A *orkspace*in Koder Linux is:

  • A persistent, named context (work, personal, client-acme).
  • Owned by exactly one Koder ID identity (one koder_user_id).
  • Backed by its own state tree, its own apps catalogue, its own

    kolide-shell settings, its own notification history, its own Koder Drive root.

  • Switchable in O(100 ms) from the kolide-shell panel without

    closing apps in other workspaces (apps are scoped to one workspace and pause/hide when their workspace is inactive).

A workspace is *ot*

  • A separate Linux user account (that's useradd, not a Kolide concept).
  • A virtual machine (no kernel isolation; one kernel, one

    compositor, one D-Bus).

  • A federation primitive (cross-device sync is a separate RFC).

D3 — Storage layout

Perworkspace XDG dirs land under a workspacescoped root:

$HOME/
└── .config/koder/workspaces/
    ├── <workspace-id>/                        # uuid v4
    │   ├── meta.toml                          # name, koder_user_id, created_at, icon
    │   ├── XDG_CONFIG_HOME/                   # $HOME/.config/koder/workspaces/<id>/XDG_CONFIG_HOME
    │   ├── XDG_DATA_HOME/                     # similar
    │   ├── XDG_CACHE_HOME/                    # similar
    │   ├── XDG_STATE_HOME/                    # similar
    │   └── kolide/                            # kolide-shell per-workspace state
    │       ├── settings.json
    │       ├── notifications.jsonl
    │       └── first-run-apps.json
    └── active                                 # symlink → <workspace-id> (current)

The kolide-services apply_setting and notifications_history_* helpers (already file-rooted at $HOME/.config/kolide/) become file-rooted at ~/.config/koder/workspaces/active/kolide/ — the symlink swap is the entirety of "switch workspace" at the storage layer.

Apps (Drive, Dek, Kortex, etc.) read XDG_*_HOME from environment. kolideservices overrides those perspawn so an app launched from inside workspace acme sees XDG_CONFIG_HOME=~/.config/koder/ workspaces/<acme-uuid>/XDG_CONFIG_HOME instead of the OS default.

D4 — Koder ID binding

libsecret schema extends from {component, user_id} to {component, user_id, workspace_id}. Each workspace has its own access+refresh token pair under its own workspace_id attribute. Login from QS in workspace A places a token there; switching to workspace B finds B's token (or none, kicking the OAuth flow).

org.koder.Kolide.Auth.StateChanged signal payload gains a fourth field: workspace_id. Consumers that don't care (legacy panels) read the first three fields and ignore.

D5 — UI surface in kolide-shell

  • *anel badge*(kolide #008) gains a small workspace icon overlay

    on the avatar. Click opens a dropdown:

` ┌─────────────────────────────────┐ │ • work (rpm@koder.dev) │ ← active │ personal (rpm@gmail.com) │ │ client-acme (acme@…) │ ├─────────────────────────────────┤ │ + New workspace… │ │ Manage workspaces… │ └─────────────────────────────────┘

`

  • *uick Settings*grows a new "Workspaces" section with the same

    dropdown plus per-workspace toggles.

  • *ock screen*asks for the active workspace's identity, not

    the OS user.

D6 — Performance budget

  • Switching workspaces (no relaunch) ≤ *00 ms*wall-clock from

    click to panel state visible.

  • App pauseondeactivate uses SIGSTOP / cgroup freezer; resume on

    reactivate. Wakelocks are tracked per-workspace to preserve battery on laptops.

D7 — Migration plan

Single-workspace systems (every install today) get a "default" workspace transparently:

  1. On first kolide-services boot after this lands, if

    ~/.config/koder/workspaces/ doesn't exist, create one workspace named default and migrate `~.configkolide

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/klinux-RFC-001-multi-tenant-desktop.kmd