KADAI under the hood
The core idea: A desktop that emerges from a description - not installed, but instantiated. Its foundation is a personal kernel: a small, encrypted, versioned self-description that can be instantiated and carried on anywhere - on any computer, in any VM, in the browser if need be. Every instance compiles the same desktop from it, compile(me.md, environment) → desktop, and whatever it learns flows back into the kernel: a system that follows the person, not the machine.
This page explains how: the data model, the key architecture, the four protocols, control through git as the control plane - and the threat model with its current gaps. Everything described here is running unless explicitly marked as design (validated August 2026, Plasma 6.6 / Ubuntu 26.04).
1 · Data model: the kernel
A Markdown file (me.md) with YAML frontmatter. Six sections with an explicit trust gradient: identity and facts (binding) · values (binding; decides trade-offs where no rule applies) · preferences (soft) · rules (inviolable without explicit consent - per action for anything that leaves the machine or cannot be undone) · episodic (current, possibly stale). Every entry carries a stable ID ({#r-001}) and a provenance line - when, by whom (human, model, machine). The frontmatter reserves a DID (did:kadai:…, so far without an external registry). Sections are meant to be addressable as MCP resources (me://rules …) - the single chokepoint where rules are enforced and access is logged.
The invariant: The kernel stores meaning, never configuration. “Focused in the morning, minimal distraction” - not “panel at the bottom, widget X”. Platform backends translate; that is why the same kernel is usable on Plasma, in a VM, or in a chat client.
Instruction and data (7 September 2026): The kernel is what the person said. The minimal instruction set is the founding page’s - six statements, three detected values, the rest derived. Observed data (devices, accounts, current working context) is not instruction: it can be collected again, lives in a store of its own (machine-local or held by the engine) and is referenced via me://. Valid means schema-conformant and encrypted to the register; current means last-updated per section plus regenerable data. Open: whether facts then still counts as “binding”.
2 · Key architecture
| Material | Type | Created | Lives | Purpose |
|---|---|---|---|---|
| Master identity | age X25519 | once, at founding (browser or trusted machine) | only passphrase-encrypted (scrypt) as master-key.age in the repo; plaintext never persisted | root of authorization: decrypts the kernel, admits new devices |
| Passphrase | human secret | chosen at founding | exclusively in your head; fed straight into scrypt when entered | guardian of the master identity - deliberately replaceable (later FIDO2/biometrics) |
| Machine identity | age, per device: Secure Enclave (Mac), TPM 2.0 (Linux), otherwise an X25519 file | at join, in the device's security chip where it has one | private: in the chip, not readable; without a chip as a file (~/.config/kadai/age.key, 0600), and the device says so; public: in the register | everyday decryption without a passphrase; on the Mac with Touch ID per opening |
| Sync key | ssh, per device: in the TPM (ecdsa) or as an ed25519 file | at join, locally | private in the chip or on the device; public as a deploy key at the repo host, with write access | transport authorization to the repo without presence, so the hourly push runs unattended - orthogonal to content encryption |
| Keyring | KeePass database + age-sealed password | on the first machine, from the seed left by founding | keyring.kdbx in the repo; its password in keyring.pass.age, encrypted to the register | the accesses the system itself needs (storage, provider tokens); every machine opens it through one script, never as a file |
| Vault | KeePass database | created by the person | vault.kdbx in the repo; password only in the person's head | the person's own passwords; no tool and no model opens it |
3 · The four protocols
Founding (once per person - in the browser, entirely client-side)
- A deterministic dialog asks for name, preferences, passphrase, repo target; language, OS and time zone are detected rather than asked; the time zone is shown once for confirmation.
- In the browser: generate the master identity (age-js) →
master-key.age(scrypt via passphrase) → generate the kernel from a template (incl. default rules r-001…r-003) → encrypt to master → register with the master pubkey. - Publication: GitLab API (token) or a copyable bundle script (armored age files as heredocs,
git init && push) for any git host. Accesses typed here (storage, provider tokens) are encrypted to the master identity and travel as the seed of the keyring. Secrets are wiped from memory after use; none of it leaves the browser unencrypted.
Join / enrollment (once per device - self-authorization)
- Two forms: the seed image for an empty machine, or a package with an assistant for a running Ubuntu (
start/). The package knows where the repo lives and carries the master identity as in the repo plus a token, encrypted to it, that may enter deploy keys. - Passphrase entry → master identity is unlocked in RAM only. The device creates machine and sync keys in the security chip (otherwise as files) and enters its sync key as a deploy key itself, through the token. Without a token it shows the key to enter by hand - the one human trust step.
git clone→ kernel decrypted with the master identity → device appends its pubkey to the register → re-encrypts kernel and keyring password to the extended recipient set → commits + pushes (“enroll”) → check: opens the kernel with its own key.- From now on, everyday operation without a passphrase; plaintext master, token and passphrase are discarded. Proven on 2 October 2026 on an old notebook, from the address in the browser to the desktop.
Every boot (steady state)
- A systemd user instance starts at boot (linger):
git pullas a merge, never a rebase →age -dwith the machine key →me.md; the orderingBefore=plasma-workspace.targetguarantees: kernel before shell. Then a push, so this machine's state goes out. - The graphical session starts (in the seed: autologin + immediate lock); the greeting reports the name + kernel commit time and, if any are waiting, proposals from a merge or a vault not yet merged.
- Reconciler: reads the shell's actual state (DBus), diffs it against
desktop-spec.jsonfrom the repo, applies only the difference. Idempotent - second run: zero operations.
Sync (continuous)
- Every 10 min: a local commit, without network and without a key. A SHA-256 guard over
me.md- only on a real change is it re-encrypted (age is not deterministic; without the guard there would be phantom commits). Changed mechanics, keyring and vault are committed along. - Push at login, logout and hourly, over the sync key without presence. If it is rejected because another machine got there first: merge, then one more try. One merge driver per file (kernel per entry, register as a union, keyring per entry through KeePassXC, vault: own version stays, the other is kept next to it). The git history doubles as the audit log (which device knew what, and when).
4 · Control: git as the control plane
There is no control port and no remote-control session. Whoever wants to change the system - human or agent, from any device - changes declarations in the repo: the kernel (meaning) or the desktop spec (derived target state). Every machine reconciles itself at its next pull. Inbound connectivity is strictly a debugging tool. This is the same move as GitOps, except that the “infrastructure” here is a person together with their working environment - and the diff-and-apply step remains necessary because Plasma's own layout load is additive (a validated finding, not an assumption).
5 · Suggestions: a role, not a fixed model; rules as policy
Whatever makes suggestions is a role, and since 24 September 2026 the direction is reversed: subsidiarity. Two ladders, capability and place (0 without a model, 1 on this device, 2 on your network, 3 with a provider). The router starts at the smallest rung that has been sufficient for this class of task so far, checks the result with deterministic criteria and ascends one rung only on measured failure, at most twice. What the person accepts or discards lands in an append-only log per machine and sets the starting rung (stay from four in five acceptances, skip below a third). The deterministic floor remains: all mechanics (sync, join, reconciler, storage, keyring) are script-based and run without any model. Before rung 3 the router shows what would leave the machine, in bytes and with the recipient, and waits for a yes per action; rules from the kernel and a ceiling in the model register bind every rung. In the lab: a 0.8-billion model on the device, a 2-billion model on the network, a provider behind consent; the entrance is KRunner, the output cards in a rail at the right screen edge, never a chat (kadai-subsidiarity.md, poc/rung/).
6 · Threat model - what holds, what doesn't (yet)
| Attack / loss | Status |
|---|---|
| Repo host compromised | reads only ciphertext (kernel) + plaintext mechanics/spec + metadata (commit times, sizes, device names in comments). Can tamper with history - commits are not signed (gap, see below). |
| Device lost/stolen | Revocation: strike the register line, re-encrypt, delete the deploy key at the host. But: old ciphertexts in the history remain readable for the old key (no re-keying of the past), and the plaintext kernel sits on the device - genuinely exposed without disk encryption. |
| Offline passphrase brute force | possible for anyone with a copy of the repo; scrypt makes it expensive but does not replace a strong passphrase. The guardian is designed to be replaceable (FIDO2/biometrics instead of a passphrase - design). |
| Malicious kernel push | whoever has write access + a register slot is an authorized machine - by design. The line of defense is the register + transport gate, not the content. |
| Gap: unsigned commits | enrollment and sync commits are not cryptographically bound to the device; device signatures (ssh signing with the deploy key) would be the natural next step. |
| Largely solved (26 September 2026): keys on disk | machine and sync keys live in the security chip where the device has one (Secure Enclave on the Mac with Touch ID, TPM 2.0 in the VM). A device without a usable chip gets file keys and says so at join. Seed still without LUKS; moving from file to chip without leaving and rejoining is missing. |
| Gap: the keyring is as safe as the weakest device | every machine in the register can open keyring.pass.age, including one with file keys. That is why the keyring holds only what the system itself needs, and the person's own passwords live in the vault that only the person opens. A machine that left has seen the system accesses: revoking it also means changing them at their services. |
| Gap: the join token | the package carries a token that may enter deploy keys, encrypted to the master identity. On GitLab.com such a token reaches across the account, on an own Forgejo across the repo's account. Narrower would be a separate account for the kernel alone or, as the next step, ssh certificates signed by the person identity: then there is no token at all. |
| Gap: no forward secrecy | static age recipients; a machine key compromised later opens every ciphertext ever sent to it. Mitigation today: rebuild the history on key loss. |
| Gap: early history | the lab system carries plaintext sample data in old commits; production systems start with a fresh history (the founding page already does this). |
| Solved (23 Sept 2026): concurrent changes | two active machines changing the same kernel: git cannot merge the ciphertext me.md.age, so it hands the file to a merge driver, .kadai-merge.py. That merges per entry: a change on one side wins, the same change on both is fine, different changes keep this machine's version and write a proposal with all three versions to ~/.config/kadai/proposals/. No timestamp decides, nothing is overwritten silently, the repository is never left mid-merge. The base is the local plaintext copy .me.md.base from the last sync; .kadai-recipients uses git's built-in union driver because the list is an OR. Still open: observed data as an append-only log per machine, union instead of merge; derived state (spec, Activities) is recomputed per machine, never merged. |
7 · Status
Validated (as of 3 October 2026): founding in the browser · joining via seed image (VM) and via package with assistant (old notebook, 2 October) · three machines in the register, active at once, merge per entry · keys in Secure Enclave and TPM, separate sync key · encrypted sync (local every 10 min, push at login/logout and hourly) with audit history · keyring and vault as KeePass files in the repo · boot ordering kernel-before-shell · idempotent reconciler · storage tier (Nextcloud, logged/reversible) · seed images arm64 and x86_64 · subsidiary router with two local models and a provider behind consent, KRunner entrance, rail (lab machine) · repo on an own Forgejo. Design: MCP module runtime with manifest/sandbox · model and rail on every machine · person identity on a FIDO key, then the phone · commit signatures · VHDX pipeline · observed data as a synced layer · the vault on the phone · moving file → chip without rejoining.