15 KiB
souveraine-lens: design
A wry mind-graph lens over a git-backed markdown vault. This file is the system's own argument: what the lens is for, who it belongs to, and how it travels from source to a signed package on a laptop. The README says how to run it; this says why it is shaped the way it is.
Two sides, one grammar
The lens reads git-backed markdown. The garden is one such tree; every agent's
memory is another. Same substance — markdown, frontmatter, [[wikilinks]],
git — and opposite ownership. So the lens is not the vault's viewer with an
agent bolted on. It is a viewer that stands in one tree at a time, and the
flip is which one.
The garden is the human's. One person first; several sharing a vault second. It is a place where attention is kept, not monitored. The agent never lives here. She comes alongside as a guest: the shell may open a note, plant one on a human's behalf, and read the garden's events over the socket — the way a partner hands a person the next card, not the way a machine carves a space for itself.
The Cloister is hers. Each agent's memory — memory/ and each cadence
under cadences/*/memory/ — is a git repo of the same shape, and the human
should be able to read it without rooting around in a filesystem. He visits;
he does not tend. The lens opens those trees read-only and shows their
history, because a memory written a commit at a time is only honestly read
that way.
That asymmetry is enforced in the process, not the page. Mode::Tend allows
save, create, move and sync; Mode::Observe refuses all four by name
before the op reaches the vault. A surface that merely hides a button has gated
nothing — and saf/identity/02-agent-principal.md is explicit that readable
does not mean owned.
The door into the cloister
The human owns the machine, so the refusal was never a permission — the files
are his uid, and $EDITOR was always one command away. What the read-only mode
actually bought was that a reach into her memory could not happen by accident,
in the same motion as tending his own garden. Keeping that and adding no door
was the wrong trade: it moved the amendment out of the lens, where it leaves no
mark she can read, and into a text editor, where it looks exactly like her own
writing in her own history.
So the door exists, it is loud, and it signs the work:
- A written reason, first. Refused if empty. The doctrine's break-glass grant has the same rule for the same purpose — a reach you cannot explain is one you should not be making.
- A live credential, second.
souveraine-stepup, the shell's own PAM service, which "must accept exactly the credentials the lockscreen accepts, no more". Not polkit and not sudo: on this laptop, on 2026-08-24, both answered authorized fororg.souveraine.stepupwith no prompt of any kind — polkit in 3.6s through an agent nobody saw, sudo from a live timestamp. A ceremony that never asks is the formalitysaf/identity/02-agent-principal.mdnames by that word. libpam isdlopened rather than linked, so a machine without it still reads every tree and refuses only the amend. - A grant that expires. Five minutes, in memory, one tree at a time, the
shell's
StepUpAuthdefault. Revoked on flip, on relinquish, on expiry, and with the process. Leaving her memory gives the hand back. syncstays refused, grant or no grant. Amending her memory is one act; pushing her archive to a remote under the human's credentials is another, and the ceremony bought only the first. Her own sync carries the amendment out.- The archive says so. Every write under a grant commits as
amend (human): …with the reason in the body. She reads her own history; that line is how she finds out, and the lens's history strip is where the human sees the same thing from the other side.
The band above the graph changes colour and stops saying guest. The two states must not be mistakable for one another at a glance.
The grammar of linking
[[wikilinks]] are the whole grammar and that is deliberate, but a vault drifts
in a specific way: the thought connected two notes and the file never said so.
The prior art converges on that gap from different directions.
- Zettelkasten proper put the structure in the name — Luhmann's
1a1folgezettel encodes where a note sits in a line of argument. Precise, and it asks a person to know the shape before they write. - Obsidian keeps the names free and pays for it with two panes: backlinks, and unlinked mentions — notes whose title appears in this note's prose without a link. The second is what turns a pile back into a graph, and it is the single most copied idea in the space.
- Roam and Logseq went below the note: block references and transclusion, where the unit of linking is a line rather than a file. Powerful, and it makes the markdown stop being plain markdown, which this vault will not trade.
- Dendron and Foam put the hierarchy back in the filename
(
proj.souveraine.lens), replacing folders with a naming convention. - Andy Matuschak's evergreen notes argue the opposite of all of it: title the note as an API, link densely, and let folders wither.
- Dataview and Logseq properties add typed links —
[[supports::x]],[[refutes::y]]— so the edge carries a claim, not just an adjacency.
What the lens takes:
- Unlinked mentions, built. Titles of four characters or more that appear in this note's prose on a word boundary, unlinked, longest first, six at most. One tap rewrites the first occurrence in place and commits it. This is the answer to "systematically linking": the system finds the edge the writing already implied and asks for one keystroke.
- A link indicator, built. The drawer head carries
n in · n out · n loose, so the note's standing in the graph is legible before scrolling. A dead[[link]]wears a⊕where it stands — an invitation, not an error. - Link-preserving moves, built. A rename rewrites
[[old-stem]]across the tree in the same commit, aliases and#anchorsintact. A link that named the note's title still resolves and is left alone. - Typed links, not built. They change the grammar every note is written in
from here on, which is the human's call and not the lens's. If they land, they
land as
[[verb::target]]parsed alongside the plain form, with the verb colouring the edge on the canvas — the graph is where a typed link would finally pay for itself.
Consequences, held as rules:
- The lens never syncs the garden's notes into the SouveraineOS documentation tree. The garden is the human's; the docs are the system's; the two do not cohabit. Reading an agent's memory is a third thing again, and it writes nothing anywhere.
- Discovery is a filesystem walk of
~/.souveraine/agents, never a call to the server. The page's closure to the network survives the second side. - A task the agent wants a human to do is a vault note the agent hands over through the limb — assigned to the human, never kept private to the agent. The authority shape for shared household tasks is decided in SouveraineOS TASK-68; the lens implements the surface when that shape lands.
- The page holds no connection to any server. Everything it shows arrived over the IPC bridge from this process; everything this process knows about the outside world is git. The mirror is closed to the network by construction, not by discipline.
The vault is files, and git is the archive
No database, no format of its own. A note is a markdown file in ~/vault
(overridable with --vault):
[[wikilinks]]and[[target|alias]]are the linking grammar. A#suffix is a heading anchor, not a note. Links that name nothing yet are dead until the note is planted — then the link lands.- Frontmatter is the same tolerant grammar as the substrate's memfs:
description,tags, and anything else round-tripped verbatim, never destroyed on rewrite. - Every save and plant is a git commit.
syncfetches and pushes with the user's own credentials, exactly as the substrate's memfs sync does. The archive is the truth; the graph is a rendering of it.
The seam
This process holds no connection to the server. The page is one embedded document; every piece of data arrives over the IPC bridge and the bridge is the only conversation:
- Page → process:
window.ipc.postMessage({id, op, root, ...})—graph,note,save,create,move,sync,status,history,diff, plusroots/rescan/flipfor the switcher andamend/relinquishfor the step-up.rootnames which tree the op is about and defaults to the active one; the page holds keys, never paths. - Process → page:
window.__lens.reply(id, payload)viaevaluate_script; the shell's reach iswindow.__lens.open(rel)andwindow.__lens.refresh(). - Every op answers, even with an error — a page waiting on a promise that never settles is a mirror with its hand stuck out.
The force layout is hand-rolled on a canvas — a few hundred nodes settle fine at O(n²) a frame, and a mirror this small deserves no third-party gravity. No dependencies, no network, one embedded document.
The face
Two skins, one structure, switched in the header or by the palette, persisted
under lens-theme:
- Warm dark, the default — charcoal
#262624glass, a spectral accent ring, mist orbs behind everything. - Light zen, the quiet — a pale canvas, deeper teal ink, the same layout breathing wider.
The structure is a sample from the kiyoshi reference, not a copy: glass panels, an editorial spacing with no forced rules, a spectral touch in the accent. What the lens adds of its own:
- The graph at the heart: drag, wheel to zoom, drag the void to pan, click to open, double-click the void to plant. Hover lights a node's neighbourhood and dims the rest.
- The switcher: a page-drawn popover, not a native select. Nineteen trees
in a
<select>rendered in the compositor's own menu, outside the skin, with four agents labelled by eight hex characters. Now: the garden, then one card per agent — a colour and monogram derived from her id, her own line about herself, her note count and how long since she last wrote — with her cadences as pills beneath. The name comes frompersona.name, then her root'sagent.json, then her cadences' ("Annie (reflection)"names its parent). Six agents on this machine; four had no name until that last hop. - The rail (left): search across title, tags and description; the hint line counts notes, links and solos. Folders remember what you opened, per tree, and a tree past forty notes starts collapsed — her memory carries thirty-nine folders and every one of them open is a wall. Drag a note onto a folder to move it; the folder counts are recursive.
- The drawer (right): the note rendered, its backlinks — "linked from"
and "links to" — mentioned, not linked, an orphan nudge that offers the
freshest five notes to link the alone one to, and a
n in · n out · n looseindicator in the head. Move renames or refolders the note and rewrites the links that named it. - The
[[picker: typed inside the editor, it mirrors the caret and lists matching notes; enter inserts, and a dead query becomes a "plant and link it" act. A link button in the drawer head does the same from the reading side, and a dead wikilink opens the plant dialog prefilled. - The command palette (
⌘K): act or open — new note, toggle theme, sync, refresh, fuzzy note search. - The first-run guide: when the garden is empty, it explains the three gestures before the void asks anything of you.
The limb
The shell drives this process over a unix socket, the rig's own pattern, one JSON object per line:
- down:
eval,state(micro-state — the freshest notes and the git truth, one payload),open,create,quit.stateandcreatealways mean the garden: an agent's memory does not belong in a sidebar card. - up: stdout lines —
note_opened,note_saved,note_created,synced,state— so a sidebar hears what the garden does without ever reaching into the vault itself. Nothing private leaks: a rel path and an event name.
The vault stays the one thing with no other readers.
Delivery
A recipe establishes a path. Delivery crosses source, package, archive,
install, then exercised behavior — each proved separately.
As observed on 2026-08-24, r20 (f8c9df3) is signed in the internal x86_64
edge repository and owns the installed laptop binary. Later source commits are
new package versions only after their own CI run publishes them; source HEAD
must not be used as installation proof.
- Source of truth: the
primarybranch; green CI (test + clippy + the attribution and action-pin gates) is the bar, on the same Gitea the substrate uses. Gitea Actions is the compiler and package gate; the laptop is an install and runtime target, not an alternate release builder. - Composition authority: SouveraineOS's
distribution/manifest.tomlnames Lens as one producer of the x86_64 and AArch64 packages. Each package job reads the package's own metadata and must pass the canonical manifest validator before its publisher runs. Lens has no device-profile membership yet. - Package: a
packaging/arch/PKGBUILDin the house's prebuilt style — CI assembles a release binary, checks its target architecture, and declares theLENS_PKGVERversion0.1.r<commits>.g<sha12>. The package owns/usr/bin/souveraine-lens, the desktop entry and the icon; the binary is the durable result, never a home-directory symlink. - x86_64 proof: r20 (
0.1.r20.gf8c9df352be3-1) is present by name insouveraine-x86_64, has a validated signature, and is package-owned on the laptop. That proves r20 packaging and installation; it says nothing about a later commit until its own package is indexed and installed. - AArch64 boundary: the second job cross-compiles against an AArch64
sysroot and assembles the same package name for
souveraine-aarch64. Until a run, archive artifact, package-owned install and device exercise are checked, that is a source path rather than AArch64 build, install or runtime proof. - Delivery: the canonical
publish-edge.shperforms a read-modify-write of the architecture's live database under one flock, signed with the house archive key, so one producer's packages do not replace another's. - License and source URL: the repo and package carry AGPL-3.0-or-later and
install the license text. Cargo names the same SPDX license. Repository
metadata and the PKGBUILD URL remain on the internal source authority until
an anonymous
souveraine-lensForge projection exists; a future public URL must resolve before either points at it.
Future
- A right-click context menu. Move, rename, link, plant, open history — the verbs exist and each one currently costs a trip to the drawer head or the palette. The rail and the canvas are where a hand already is.
- The shared-task lane, when TASK-68's authority shape lands: the agent assigns human tasks through the vault; the lens surfaces them as notes.
- Typed links, if the grammar is worth changing — see above.
- Public Forge projection and anonymous source URLs.