Architecture
discovery providers → machine registry → policy → desired mounts → reconciler → mount drivers → ~/machines/<id>The pipeline
Section titled “The pipeline”- Discovery providers report machines: static config, Tailscale, DNS TXT and HTTP. Each runs on its own interval and knows nothing about mounting.
- The registry keeps every provider’s latest reports. A report that stops arriving expires after
max(TTL, 3 × interval). A provider that fails keeps its last good view, frozen, so an outage never looks like every machine vanishing. - Policy gives each machine a verdict: allowed, discover-only or denied. When providers disagree, the most trusted report wins outright (static, Tailscale, HTTP, DNS). See Policy.
- Desired mounts are computed from the allowed machines: static mounts from
[[machines]], one mount per discovered machine from its provider’s[discovery.mount]template, minus anything you have held down withbifrost unmount. - The reconciler compares desired mounts with what is actually mounted and picks one action per mount: mount, unmount, remount, or wait. See Reconciliation.
- Mount drivers (sshfs, rclone, rclone-nfs) do the work, always through your system
ssh. They know nothing about how a machine was found.
Components
Section titled “Components”| Binary | Role |
|---|---|
bifrostd |
The daemon: discovery, policy, reconciliation and the local API. It owns every mount’s lifecycle |
bifrost |
The CLI, a client of the daemon’s API |
bifrost-tui |
The terminal UI, another client of the same API |
The workspace has eight crates:
| Crate | Contents |
|---|---|
bifrost-core |
Models, validation, policy, registry and the reconcile planner. No I/O |
bifrost-config |
Parsing and validating config.toml, default paths |
bifrost-discovery |
The Tailscale, DNS and HTTP providers |
bifrost-mount |
The sshfs, rclone and rclone-nfs drivers, the mount table, health probes |
bifrost-client |
The HTTP-over-Unix-socket client |
bifrost-daemon |
bifrostd |
bifrost-cli |
bifrost |
bifrost-tui |
bifrost-tui |
Inside the daemon
Section titled “Inside the daemon”One task, the actor, owns all mutable state: the config, the registry, every mount’s runtime state, the holds and the event log. Everything else sends it messages: provider results, finished mount operations, health results, timer ticks, API calls and config reloads. There are no locks on domain state, and the actor never waits on I/O or touches a mount path itself.
A pass runs whenever something changes:
| Trigger | When |
|---|---|
| Startup | Right after existing mounts are adopted |
| Discovery | Each provider result |
| Health | Every health_interval, for each mounted path |
| Fallback | Every reconcile_interval, with a driver re-probe |
| Deadlines | The next retry or grace period that expires |
| Child exit | A mount helper exited; that mount is probed at once |
| Config | A file change, SIGHUP or bifrost config reload |
| API | mount, unmount, discover, reconcile |
Mount and unmount calls run as separate tasks under timeouts and report back as messages. Health probes on a live mount run in a blocking thread with a 5 second timeout, so a hung FUSE mount can’t stall the daemon.
Design principles
Section titled “Design principles”- Discovery and mounting are separate.
- Discovery never equals authorization.
- Drivers never know how machines were discovered.
- Providers never know how filesystems are mounted.
- The daemon owns lifecycle.
- The CLI and TUI are clients of the daemon.
- Desired state is declarative.
- Reconciliation is idempotent.
- External tools are replaceable.
- The core library stays independent of SSHFS, rclone, Tailscale and DNS.
- A future native mount protocol must fit without redesigning the product.
- An external discovery plugin must fit without redesigning the product.

