Skip to content

Architecture

soft-fig is a Cargo workspace of fifteen small Rust crates. This page is the map: what each crate is responsible for, how a change flows from your editor to a signed commit, and the two boundaries that shape everything — frontend-neutral cores and the daemon owns all writes.

It describes the code as built. The design behind it — per-pillar specs and a dated decision log — lives in the garden, as explained on the roadmap page. When this page and the code disagree, the code wins.

Frontend-neutral cores. The crates that do the real work — crypto, storage, version control, sync, deployment, scaffolding — know nothing about any user interface and don’t depend on the daemon. They expose plain library entry points, so the CLI, the TUI, an MCP tool, and the tests all call the same functions. For example, softfig onboard and any future onboard MCP tool both wrap one softfig-onboard entry point.

The daemon owns all writes. While the per-device daemon runs, it is the sole writer to the store. Every mutation — an IPC verb, a write through the FUSE mount, a file the watcher noticed — funnels through one classifier and one signer inside softfig-keeperd. Frontends never touch the object store directly. The CLI keeps a direct-mode fallback for commit, log, show, and fsck, but it engages only when no daemon socket exists; a reachable daemon that returns an error is surfaced verbatim, never bypassed.

Source: crates/.

Crate Job
softfig-vault Keys and crypto: the random key K, master key generations, blob encryption, Layer B subkeys, the Ed25519 identity and X25519 transport keys, shared-subtree keys, relock tokens, and recovery (policy, escrow, rewrap).
softfig-store The low-level store: content-addressed ciphertext objects plus a SQLite metadata database.
softfig-vcs Version control over the store: the garden walker, tree builder, signed commits, the closed intent enum, log, fsck, per-chain garbage collection, and the chain registry that routes a path to the device chain or a shared chain.
softfig-net Cross-device transport: a Noise tunnel over TCP, a protobuf control plane, pairing, the signed peer ring, mDNS discovery, the blind relay, replication, and the shared-key ceremony.
softfig-deploy The dotfile deploy spine: plans and applies config/deploy.toml as symlinks to a plaintext cache, or stamped copies.
softfig-onboard The scaffold: materializes a fresh garden from an embedded skeleton and inits the vault, born-in-FUSE.
Crate Job
softfig-keeperd The per-device daemon. Holds the unlocked vault session in RAM, serves the IPC socket, runs the watcher, hosts the FUSE mount and the softfig-net listener, and commits and signs every write. Also starts and stops the growlightd user unit.
softfig-fuse The FUSE filesystem: a plaintext view of the encrypted store, decrypted at read time, with Layer B secrets redacted to placeholders and a write overlay. Composes every enabled chain into one namespace.
Crate Job
softfig-ipc The JSON-Lines protocol for both daemons: request/reply envelopes, typed verb arguments, error kinds, queue validation, and a synchronous Unix-socket client. Every frontend speaks this.
Crate Job
softfig-cli The softfig binary: onboarding, vault, VCS, daemon, migrate, deploy, pairing, replica, shared-subtree, and growlight subcommands.
softfig-tui A ratatui terminal UI over the daemon: browse, history, actions, vault, peers, deploy, backup, and a read-only Growlight view.
softfig-mcp A stateless stdio bridge from Model Context Protocol JSON-RPC to IPC verbs, so a Claude session maintains the garden through typed tools. Vault operations are deliberately not exposed.
Crate Job
softfig-growlightd The orchestrator daemon and the single control plane for every growlight session: owns the agent fleet, the queue scheduler, leases, and the coordination bus, behind its own socket. A one-agent run is a fleet of one. It is a keeperd client, kept as a separate process so vault security never shares a fate with agent crashes.
softfig-growlightd-client A thin reconnecting subscribe client shared by the CLI and the GUI.
softfig-growlight-gui The fleet console: an Elm-style view-model, plus an iced window behind the off-by-default gui Cargo feature.

Dependencies point one way: frontends depend on softfig-ipc (the CLI also on the cores for its direct mode), softfig-keeperd depends on the cores and the FUSE crate, and the cores never depend on a daemon or a frontend. Growlight is covered in the Growlight area.

Two paths reach a signed commit, and they share one pipeline.

Typed write. A frontend sends a verb — log_decision, add_note, commit, replica_grant — as one JSON line over the Unix socket. The daemon authenticates the caller (SO_PEERCRED UID match on a 0600 socket), validates the arguments, stamps the garden conventions (path, header, intent), and writes one commit. Because the daemon stamps the conventions, any Claude session writes a correctly-formed file without having learned them.

Native write. You, an editor, or a tool writes a file inside the mounted garden. The write lands in the FUSE overlay, the watcher’s accumulator debounces it, and the intent classifier labels the change (decision_logged, incident_logged, archive_move, or the manual_edit fallback) before the daemon commits it.

Both feed the same dirty-set accumulator and classifier, so one code path turns dirt into a signed commit. Details: Daemon & FUSE.

┌── CLI ──┐ ┌── TUI ──┐ ┌── MCP ──┐
typed write │ verb │ │ verb │ │ verb │
└────┬────┘ └────┬────┘ └────┬────┘
└─────────────┼─────────────┘
▼ JSON-Lines / Unix socket (softfig-ipc)
┌───────────────┐
native write ─FUSE──▶ │ softfig- │ ─▶ classify ─▶ sign ─▶ commit
(editor, tool) │ keeperd │ (softfig-vcs → softfig-store)
└──────┬────────┘
│ decrypt at read time
native read ◀──FUSE──────────┘

Reads also take two forms: native reads through the mount (decrypted in the daemon, Layer B secrets projected as placeholders), or the read-only list_tree and read_file verbs, which apply the same redaction.

Across devices. softfig-keeperd also hosts the softfig-net listener. Peers exchange signed ciphertext over Noise sessions that never touch the local socket: backups to granted hosts, and edits to shared subtrees between their members. See The Vault → Device identity and trust.

The daemon’s canonical state lives under a state root (default ~/.local/share/softfig/<garden-dir-name>/ for a fresh onboard), separate from the garden root you edit:

<state_root>/.softfig/
├── vault/ # wrapped keys, identity, recovery policy, escrow record
├── objects/<aa>/<rest> # ciphertext blobs, addressed by BLAKE3(ciphertext)
├── db.sqlite # commits / trees / tree_entries / refs / meta
└── keeper.toml # bootstrap pointer (state_root + a few pre-unlock flags)

The garden root holds no plaintext at rest; the daemon serves it through FUSE on unlock and it vanishes on lock. Per-garden configuration (network, relay, replica, shared subtrees, peers, deploy) lives inside the garden under config/, so it is encrypted and versioned like everything else; only the small bootstrap pointer stays in plaintext beside the vault. See Config files.


Related: Daemon & FUSE, Version Control, The Vault, and Status (what ships in this release).