InternalsDaemon

Daemon Bin

Daemon Bin

The main binary entry point for the Unbound daemon. It wires together all specialized crates into a single long-running process that serves the macOS app over Unix domain sockets.

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        unbound-daemon                                │
│                                                                      │
│  CLI (clap)                                                          │
│    ├── start [--foreground]                                          │
│    ├── stop                                                          │
│    └── status                                                        │
│                                                                      │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                      IPC Server (Unix Socket)                  │  │
│  │                                                                │  │
│  │  health ─── auth ─── session ─── message ─── repository       │  │
│  │  claude ─── terminal ─── git ─── subscription                 │  │
│  └────────────────────────────────────────────────────────────────┘  │
│                              │                                       │
│              ┌───────────────┼───────────────┐                       │
│              ▼               ▼               ▼                       │
│         ┌────────┐    ┌──────────┐    ┌──────────┐                  │
│         │ Armin  │    │   Deku   │    │ Git Ops  │                  │
│         │(state) │    │ (Claude) │    │  (git)   │                  │
│         └────────┘    └──────────┘    └──────────┘                  │
│              │                                                       │
│     ┌────────┼────────┐                                             │
│     ▼        ▼        ▼                                             │
│  Toshinori  Levi  AblyRealtime  SafeFileOps                              │
│  (sink)    (cold)   (hot)      (files)                              │
└─────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│  Platform Services: SQLite, Keychain, Supabase, Claude CLI          │
└─────────────────────────────────────────────────────────────────────┘

CLI

unbound-daemon start [--foreground]   # Start the daemon
unbound-daemon stop                   # Stop the daemon gracefully (or force kill)
unbound-daemon status                 # Check if daemon is running + version
unbound-daemon --log-level debug ...  # Set log verbosity (default: info)

Initialization Sequence

On start, the daemon boots services in dependency order:

  1. Singleton check - Ensure no other daemon is running (via socket probe)
  2. File system setup - Create directories, write PID file
  3. IPC server - Bind Unix socket
  4. Toshinori - Supabase sync sink
  5. Armin - Session engine (SQLite-backed)
  6. Database - Async SQLite executor
  7. SecretsManager - Platform keychain access
  8. SupabaseClient - REST API client
  9. Levi - Supabase message sync worker (cold path)
  10. daemon-ably sidecar - Shared Ably transport process (only when authenticated context exists)
  11. AblyRealtimeSyncer + Falco sidecar - Hot-path message publish chain (Armin -> Falco -> daemon-ably -> Ably)
  12. Nagato server + Nagato sidecar - Remote command ingress bridge (Ably -> daemon-ably -> Nagato -> daemon)
  13. Sidecar log capture - Stream sidecar stdout/stderr into observability
  14. SafeFileOps - Rope-backed file I/O with cache
  15. Handler registration - Wire IPC methods to handlers
  16. Listen - Accept client connections

The daemon starts the Ably token broker (~/.unbound/ably-auth.sock) and mints audience-scoped tokens. daemon-ably receives those broker credentials and exposes one local socket at ~/.unbound/ably.sock. Falco and Nagato only receive UNBOUND_ABLY_SOCKET; they never receive broker tokens or raw Ably API keys.

Shared State

All handlers share a DaemonState (cheap to clone via Arc):

FieldTypePurpose
agent-session-sqlite-persist-coreArc<DaemonArmin>Session engine (snapshot + delta views)
dbAsyncDatabaseThread-safe SQLite executor
secretsArc<Mutex<SecretsManager>>Platform keychain access
safe-file-opsArc<SafeFileOps>Cached file I/O
configArc<Config>Supabase URLs, relay config
pathsArc<Paths>Socket, PID, database paths
toshinoriArc<ToshinoriSink>Supabase change sink
message_syncArc<Levi>Message sync worker
realtime_message_syncOption<Arc<AblyRealtimeSyncer>>Ably hot-path message sync worker
supabase_clientArc<SupabaseClient>REST API for device management
session_syncArc<SessionSyncService>Background session sync
session_secret_cacheSessionSecretCacheIn-memory secret lookup
claude_processesArc<Mutex<HashMap>>Active Claude CLI processes
terminal_processesArc<Mutex<HashMap>>Active terminal processes

IPC Handlers

Every IPC method maps to a handler that extracts params, validates, delegates to a crate, and returns a JSON response.

DomainMethodsBacking Crate
Healthhealth, shutdown, outbox.status-
Authauth.login, auth.complete_social, auth.status, auth.logoutauth-engine
Sessionssession.list, session.create, session.get, session.deletearmin
Messagesmessage.list, message.sendarmin
Reposrepository.list, repository.add, repository.removearmin
Filesrepository.list_files, repository.read_file, repository.write_file, ...safe-file-ops, safe-repo-dir-lister
Claudeclaude.send, claude.status, claude.stopdeku, eren-machines
Terminalterminal.run, terminal.status, terminal.stoperen-machines
Gitgit.status, git.diff_file, git.log, git.branches, git.stage, ...git-ops
GitHubgh.auth_status, gh.pr_create, gh.pr_view, gh.pr_list, gh.pr_checks, gh.pr_mergegh-cli-ops
Streamingsession.subscribe, session.unsubscribedaemon-ipc

Side-Effect Bridge

The armin_adapter composes two sinks so every Armin commit fans out:

Armin commit
    ├──► DaemonSideEffectSink  -> broadcast to IPC clients
    └──► ToshinoriSink
            ├──► Levi (cold path) -> Supabase messages/session tables
            └──► AblyRealtimeSyncer (hot path) -> Falco -> daemon-ably -> Ably

MessageAppended is fanned out to both cold and hot paths. Hot path uses channel session:{session_id}:conversation with event conversation.message.v1.

Crate Structure

daemon-bin/
├── Cargo.toml
└── src/
    ├── main.rs                     # CLI entry point (clap)
    ├── app/
    │   ├── init.rs                 # Boot sequence
    │   ├── lifecycle.rs            # Stop / status commands
    │   └── state.rs                # DaemonState definition
    ├── auth/
    │   ├── login.rs                # OAuth + device registration
    │   ├── logout.rs               # Token revocation + cleanup
    │   └── status.rs               # Session validity check
    ├── ipc/
    │   ├── register.rs             # Handler registration
    │   └── handlers/
    │       ├── health.rs
    │       ├── git.rs
    │       ├── message.rs
    │       ├── session.rs
    │       ├── repository.rs
    │       ├── claude.rs
    │       └── terminal.rs
    ├── machines/
    │   ├── claude/stream.rs        # Claude event → Armin bridge
    │   ├── terminal/stream.rs      # Terminal output → Armin bridge
    │   └── git/operations.rs       # Git Ops wrappers
    ├── armin_adapter.rs            # Composite side-effect sink
    └── utils/
        ├── secrets.rs              # Key derivation helpers
        └── session_secret_cache.rs # In-memory secret cache

Lifecycle

Startup: Singleton check → service init → handler registration → listen loop

Shutdown: shutdown IPC method → tokio cancellation → cleanup PID + socket files

Stop command: Sends shutdown IPC call → waits 3s → SIGKILL if still running

Token Auth Migration Checklist

  • api/v1/mobile/ably/token supports audience-scoped token issuance (daemon_falco / daemon_nagato / mobile audiences).
  • Daemon Ably broker (~/.unbound/ably-auth.sock) is running and sidecars authenticate with broker tokens.
  • daemon-ably receives broker envs (UNBOUND_ABLY_BROKER_SOCKET, UNBOUND_ABLY_BROKER_TOKEN_FALCO, UNBOUND_ABLY_BROKER_TOKEN_NAGATO).
  • Runtime sidecars (falco, nagato) use local transport env only (UNBOUND_ABLY_SOCKET).
  • iOS clients use token auth callback only (no ABLY_API_KEY fallback path).
  • Logout clears broker token cache, tears down daemon-ably/Falco/Nagato sidecars, and removes ably.sock, falco.sock, nagato.sock.
  • Manual key rotation reminder: rotate legacy Ably API keys in server-side secret storage and revoke old keys after rollout verification.

Presence Heartbeat Contract

daemon-ably emits message-based presence heartbeats for iOS remote-command gating.

FieldValue
Channelsession:presence:{user_id}:conversation
Eventdaemon.presence.v1
Producerdaemon-ably
Status valuesonline, offline
Online cadenceimmediate publish on startup + periodic heartbeat
Offline signalbest-effort publish during graceful shutdown

Payload schema:

{
  "schema_version": 1,
  "user_id": "user-uuid",
  "device_id": "device-uuid",
  "status": "online",
  "source": "daemon-ably",
  "sent_at_ms": 1739030400000
}

Regression Matrix

Use this matrix for QA and release checks after sidecar changes:

ScenarioExpected Result
Falco side-effect publish with channel/event/payload overridesOverride behavior matches pre-migration output exactly
Nagato command handling under loadOne-in-flight processing remains enforced
Nagato daemon timeoutFail-open behavior publishes timeout ACK and continues
daemon-ably restart while daemon stays upFalco/Nagato recover transport without process restart
Login/logout transitionsSidecars start/stop deterministically and sockets are cleaned
iOS target-device gatingRemote actions disable after heartbeat TTL and re-enable after next heartbeat

Dependencies

This crate depends on nearly every other workspace crate:

  • armin - Session/message storage engine
  • daemon-config-and-utils - Config, paths, logging, crypto primitives
  • daemon-ipc - Unix socket server and protocol
  • daemon-database - Async SQLite persistence
  • daemon-storage - Platform keychain
  • auth-engine - OAuth flow and token/session management
  • toshinori - Supabase sync sink
  • levi - Message sync worker
  • daemon-ably (runtime process) - Shared Ably transport + heartbeat publisher
  • daemon-falco (runtime process) - Ably publisher for hot-path payloads
  • daemon-nagato (runtime process) - Remote command ingress sidecar
  • deku - Claude CLI process manager
  • git-ops - Native git operations (libgit2)
  • safe-file-ops - Rope-backed file I/O
  • safe-repo-dir-lister - Directory listing
  • eren-machines - Process registry and event bridge
  • historia-lifecycle - PID/singleton management
  • session-lifecycle-orchestrator - Session orchestration
  • sakura-working-dir-resolution - Working directory resolution
  • device-identity-crypto - Device identity and key management