Thrum Daemon Architecture

TL;DR: The daemon is the one process everything talks to. Start it with thrum init (auto-starts) or thrum daemon start. It handles messaging, sync, the web UI, and real-time notifications — all on one port. You rarely interact with it directly.

See also: System Overview for how the daemon fits into the larger Thrum ecosystem.

Overview

The Thrum daemon is a background service that manages the .thrum/ directory and the sync worktree (at .git/thrum-sync/a-sync/ on the a-sync orphan branch), handles client connections via Unix socket and WebSocket, serves the embedded web UI, and coordinates message synchronization with Git. It serves as the central coordinator for all Thrum clients (CLI, Web UI, and MCP server).

Architecture

┌─────────────┐  ┌──────────────┐  ┌──────────────┐
│   CLI       │  │   Web UI     │  │  MCP Server  │
│  (client)   │  │  (browser)   │  │ (thrum mcp)  │
└──────┬──────┘  └──────┬───────┘  └──────┬───────┘
       │ Unix socket     │ WebSocket       │ Unix socket
       │ JSON-RPC 2.0    │ JSON-RPC 2.0    │ JSON-RPC 2.0
       ▼                 ▼                 ▼
┌──────────────────────────────────────────────────┐
│                     Daemon                        │
├──────────────────────────────────────────────────┤
│ • Unix Socket Server    (.thrum/var/thrum.sock)   │
│ • WebSocket + SPA       (localhost:9999)           │
│ • Lifecycle (flock, JSON PID, defer cleanup)       │
│ • RPC Handlers          (agent, message, sync)     │
│ • Sync Loop             (60s interval)             │
│ • Stale Context Cleanup                            │
└──────────────────┬───────────────────────────────┘
                   │
                   ▼
  .git/thrum-sync/a-sync/    (worktree on a-sync branch)
  ├── events.jsonl           (agent lifecycle events)
  └── messages/              (per-agent message logs)
      └── {agent_name}.jsonl

  .thrum/
  ├── config.json            (daemon configuration + identity block)
  ├── config.json.pre-identity-bak  (one-time backup on first daemon_id rotation)
  ├── var/
  │   ├── thrum.sock         (Unix socket)
  │   ├── thrum.pid          (JSON PID file)
  │   ├── thrum.lock         (flock for SIGKILL resilience)
  │   ├── ws.port            (WebSocket port number)
  │   ├── sync.lock          (sync loop file lock)
  │   └── messages.db        (SQLite projection)
  ├── identities/            (per-worktree agent identity files)
  │   └── {agent_name}.json
  └── redirect               (feature worktrees only)

Components

1. Unix Socket Server

Location: internal/daemon/server.go

The socket server implements JSON-RPC 2.0 protocol for client-daemon communication.

Key features:

  • Listens on .thrum/var/thrum.sock
  • Accepts multiple concurrent connections
  • Dispatches requests to registered handlers
  • Handles JSON-RPC 2.0 protocol correctly
  • Sets socket permissions to 0600 (owner-only)
  • Detects and removes stale sockets from dead daemons

JSON-RPC 2.0 format:

// Request
{
  "jsonrpc": "2.0",
  "method": "health",
  "params": {},
  "id": 1
}

// Response (success)
{
  "jsonrpc": "2.0",
  "result": {"status": "ok"},
  "id": 1
}

// Response (error)
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32601,
    "message": "Method not found"
  },
  "id": 1
}

2. PID File Management

Location: internal/daemon/pidfile.go

Manages process ID file with JSON metadata for daemon lifecycle.

JSON PID format:

{
  "pid": 12345,
  "repo_path": "/Users/leon/dev/myproject",
  "started_at": "2026-02-08T12:00:00Z",
  "socket_path": "/Users/leon/dev/myproject/.thrum/var/thrum.sock"
}

Functions:

  • WritePIDFileJSON(path, info) - Write JSON PID with metadata
  • ReadPIDFileJSON(path) - Read JSON PID (falls back to plain integer format for backward compatibility)
  • CheckPIDFileJSON(path) - Check if process is running and return PID info
  • ValidatePIDRepo(info, repoPath) - Verify PID belongs to the same repository
  • RemovePIDFile(path) - Clean up PID file

PID file location: .thrum/var/thrum.pid

Features:

  • JSON format with repository affinity (repo path, socket path, start time)
  • Backward-compatible reader supports plain integer format
  • Pre-startup duplicate detection prevents multiple daemons per repo
  • Automatic cleanup on shutdown

3. File Locking (flock)

Location: internal/daemon/flock.go, internal/daemon/flock_unix.go, internal/daemon/flock_other.go

Provides SIGKILL-resilient daemon process detection using OS-level file locking.

How it works:

  • Uses syscall.Flock() with LOCK_EX|LOCK_NB for exclusive non-blocking lock
  • Lock is held on .thrum/var/thrum.lock for the daemon's entire lifetime
  • The OS automatically releases the lock when the process dies, even on SIGKILL
  • Non-unix platforms have no-op stubs (lock detection falls back to PID file only)

Key functions:

  • AcquireLock(path) - Try to acquire exclusive lock, returns error if held
  • FileLock.Release() - Release lock and remove lock file (idempotent, nil-safe)
  • IsLocked(path) - Check if lock is currently held (unix only)

4. Lifecycle Management

Location: internal/daemon/lifecycle.go

Manages daemon startup, signal handling, and graceful shutdown with defense-in-depth cleanup.

Startup sequence (Lifecycle.Run()):

  1. Acquire file lock (flock) for SIGKILL resilience
  2. Pre-startup validation: check for existing daemon (repo affinity)
  3. Write JSON PID file with metadata
  4. Register defer safety net (catches panics, early returns)
  5. identity.Bootstrap(thrumDir, repoPath) — load or create the daemon's persistent identity (ULID-based daemon_id, repo metadata); atomically rewrites .thrum/config.json; writes .thrum/config.json.pre-identity-bak on first rotation (backup-once, never overwritten)
  6. Populate daemon_identity SQLite table via state.NewState — single-row mirror of the config.json identity block; written on every startup via INSERT OR REPLACE
  7. Register @supervisor_<project> pseudo-agent (idempotent)
  8. ReloadOnBoot — re-enqueue any pending permission nudges that survived a daemon restart
  9. Backfill runtime field in all identity files where it is missing
  10. Start Unix socket server
  11. Start WebSocket server (if configured), write port file
  12. Start monitor supervisor (respawns persisted monitor jobs from DB)
  13. ConnectAll peers, then ReconcileAll (2-second settling window, up to 4 peers in parallel) — attempts peer.repair for any peer whose address may have drifted since last shutdown
  14. Start signal handler goroutine
  15. Wait for shutdown signal

Signal handling:

  • SIGTERM - Graceful shutdown
  • SIGINT - Graceful shutdown
  • SIGHUP - Reserved for config reload (future)

Shutdown sequence:

  1. Stop WebSocket server, remove port file
  2. Stop Unix socket server (waits up to 5s for in-flight requests), remove socket
  3. Remove PID file
  4. Release file lock

Defer cleanup safety net:

  • A defer block in Run() catches any exit path (panic, early return, unexpected error)
  • Uses atomic.Bool to prevent double cleanup with the normal shutdown path
  • All cleanup operations are idempotent (safe to run twice)

5. RPC Handlers

Location: internal/daemon/rpc/

RPC method handlers implement daemon functionality. All handlers are registered on both the Unix socket and WebSocket servers unless noted.

Registered handlers:

Category Methods Notes
Health health
Agent agent.register, agent.list, agent.whoami, agent.listContext, agent.delete, agent.cleanup delete and cleanup are Unix socket only
Session session.start, session.end, session.list, session.heartbeat, session.setIntent, session.setTask
Message message.send, message.get, message.list, message.edit, message.delete, message.markRead
Sync sync.force, sync.status Both Unix socket and WebSocket
User user.register, user.identify user.register restricted to WebSocket transport
Monitor monitor.start, monitor.list, monitor.show, monitor.stop, monitor.logs, monitor.restart Unix socket only — absent from peer/Telegram dispatchers

See RPC API Reference for full documentation.

Caller Identity Enforcement (v0.9.0):

Every RPC request on the Unix socket goes through a two-stage identity check:

  1. Peercred resolution — the accept loop extracts the connecting process's kernel-verified PID via SO_PEERCRED (Linux) or LOCAL_PEERPID (macOS), resolves the PID's working directory, walks to the git root, and matches against registered agent worktrees. Matched callers get a verified identity; callers whose CWD resolves to a git root that doesn't match any registered worktree are classified as anonymous. Since v0.9.1, callers whose introspection failed outright (kernel refused peer creds, gopsutil.Cwd errored on a short-lived subprocess) are not classified as anonymous — the resolver returns a raw error and the daemon falls through to legacy client-asserted identity. slog.Warn at step=pid failed or step=cwd failed captures the introspection failure for diagnostics.

  2. Anonymous allowlist — anonymous callers may only invoke a hardcoded set of ~30 read-only methods (observability, team/session/message queries, tmux status reads, and the bootstrap RPCs agent.register, session.start, session.setIntent). All mutating RPCs are rejected before the handler runs. Since v0.9.1 this rejection applies only to provably anonymous callers; see Security Model for the full allowlist.

The G4 liveness gate additionally guards all daemon-side writes to identity files: if the target agent's PID is no longer alive, the write is refused and an identity_guard_fire slog event is emitted. See below for the event schema.

Health check response:

{
  "status": "ok",
  "uptime_ms": 12345,
  "version": "1.0.0",
  "repo_id": "abc123",
  "sync_state": "synced"
}

6. WebSocket Server and Embedded SPA

Location: internal/websocket/, internal/web/

The WebSocket server provides real-time communication and serves the embedded web UI on a single port.

Key features:

  • Full JSON-RPC 2.0 support (same protocol as Unix socket)
  • Real-time notification.message push events to connected sessions
  • Client registry for tracking connections by session_id
  • Default port: 9999 (configurable via THRUM_WS_PORT)
  • Embedded React SPA served from the same port

Route layout (with UI):

  • /ws - WebSocket upgrade handler
  • /assets/ - Static file server (with immutable cache headers)
  • / - SPA fallback (serves index.html for all other paths)

Route layout (without UI):

  • / - WebSocket handler (backward compatible)

Embedded SPA (internal/web/embed.go):

  • Uses //go:embed all:dist to bundle the React UI into the Go binary
  • Build pipeline: pnpm build -> copy to internal/web/dist/ -> go build
  • Dev mode: set THRUM_UI_DEV=./ui/packages/web-app/dist to serve from disk

WebSocket Origin Restriction (v0.9.0):

The WebSocket upgrade handler validates the browser Origin header against a localhost allowlist before completing the handshake:

  • http://localhost:<port> and ws://localhost:<port>
  • http://127.0.0.1:<port> and ws://127.0.0.1:<port>

Connections from any other origin receive HTTP 403 Forbidden at the upgrade handshake and are dropped before any RPC can execute. This prevents cross-origin WebSocket hijacking (CSRF via a remote page). Pre-v0.9.0 the CheckOrigin callback returned true unconditionally. See Security Model for the full local trust stack.

Browser auto-registration:

  • user.identify RPC extracts git config user.name and user.email for the repo
  • user.register RPC (WebSocket-only) registers browser users with kind="user"
  • Idempotent: returns existing user info if already registered

Components:

  • server.go - HTTP server with mux routing, WebSocket upgrade, SPA handler
  • connection.go - Per-connection read/write loops
  • handler.go - Handler registry interface
  • client_registry.go - Tracks connected clients by session_id

7. Sync Loop Integration

Location: internal/sync/loop.go, internal/daemon/rpc/sync_rpc.go

The sync loop runs as a goroutine within the daemon, periodically synchronizing JSONL data via Git.

Key features:

  • Default interval: 60 seconds (configurable via --sync-interval flag)
  • Sync worktree at .git/thrum-sync/a-sync/ (uses git-common-dir for nested worktree support)
  • File lock (sync.lock) prevents concurrent sync operations
  • Manual sync via sync.force RPC method

Sync cycle:

  1. Acquire sync lock (.thrum/var/sync.lock)
  2. Fetch remote (git fetch in sync worktree)
  3. Merge all JSONL files (events.jsonl + messages/*.jsonl) with dedup
  4. Apply new events to SQLite projection
  5. Push merged changes back to remote
  6. Release lock

RPC methods:

  • sync.force - Trigger manual sync (non-blocking), returns current status
  • sync.status - Return current sync state (stopped, idle, synced, error)

8. Agent Work Context (Live Git State)

Location: internal/gitctx/, internal/daemon/cleanup/

Tracks what each agent is working on in real-time by extracting Git state on heartbeat.

internal/gitctx/ package:

Extracts git-derived work context from a worktree path:

  • WorkContext struct: branch, worktree path, unmerged commits, uncommitted files, changed files, extraction timestamp
  • CommitSummary struct: SHA, message (first line), list of changed files
  • ExtractWorkContext(worktreePath) - Runs git commands to gather state (~80ms)
    • git branch --show-current - Current branch
    • git log --oneline base..HEAD - Unmerged commits vs origin/main or origin/master
    • git diff --name-only base...HEAD - Changed files vs base branch
    • git status --porcelain - Uncommitted/staged files

RPC methods:

  • session.heartbeat - Extracts and stores git context automatically
  • session.setIntent - Agent sets work intent (free text)
  • session.setTask - Agent sets current task description
  • agent.listContext - Query all agent work contexts (filterable by agent_id)

Cleanup logic (internal/daemon/cleanup/):

  • CleanupStaleContexts(db, now) - Removes stale work contexts from SQLite
    • Rule 1: Contexts > 24h old with no unmerged commits
    • Rule 2: Contexts from sessions ended > 7 days ago
    • Rule 3: Contexts where git data was never collected
  • FilterStaleContexts(contexts, now) - In-memory filter (same rules, for sync)
  • Runs at daemon startup and before sync

Observability

identity_guard_fire Slog Event

Every identity guard outcome (whether the guard fires and denies, fires and allows, auto-reclaims, or is skipped) emits a structured slog event with the key identity_guard_fire. Operators and monitoring pipelines can subscribe to this event for identity violation alerts.

Event fields:

Field Type Description
guard string Guard key that fired (e.g., cross_worktree, daemon_writer_liveness, unauthenticated_rpc)
mode string Enforcement mode at fire time: strict, warn, or off
outcome string denied, allowed, auto_reclaimed, or skipped
reason string Machine-readable reason code (e.g., subject_pid_dead, identity_mismatch)
caller_pid integer PID of the connecting process (when available via peercred)
target_pid integer PID of the identity file's recorded agent_pid (when relevant)

The identity_guard_fire event is emitted on every outcome — including successful passes — so operators can audit both denials and auto-reclaims without separate event types.

Local-Only Mode

When your repository is public, the daemon's sync loop pushes the a-sync branch to origin, which would expose private agent messages. Local-only mode disables all remote git operations while keeping everything else working.

Enable local-only mode

# Via CLI flag
thrum daemon start --local

# Via environment variable
THRUM_LOCAL=1 thrum daemon start

The setting persists in .thrum/config.json:

{ "local_only": true }

Priority order: CLI flag > environment variable > config file > default (true via thrum init).

What changes in local-only mode

Component Normal mode Local-only mode
Messaging Works Works
Sessions Works Works
SQLite projection Works Works
WebSocket / MCP Works Works
git push origin a-sync Every 60s Skipped
git fetch origin a-sync Every 60s Skipped
Remote branch setup Automatic Skipped

Check if local-only mode is active

thrum sync status
# Shows "Mode: local-only" or "Mode: normal"

thrum sync force
# Shows "local-only (remote sync disabled)" when active

9. State Management

Location: internal/daemon/state/

The State struct manages the daemon's persistent state: JSONL event logs and SQLite projection.

Constructor: NewState(thrumDir, syncDir, repoID) splits paths:

  • thrumDir (/path/to/.thrum/) - Runtime files: var/messages.db, var/thrum.sock, etc.
  • syncDir (/path/to/.git/thrum-sync/a-sync/) - JSONL data: events.jsonl, messages/*.jsonl

Event routing (per-agent JSONL sharding):

  • message.* events -> messages/{agent_name}.jsonl (per-agent file, keyed by message author)
  • All other events -> events.jsonl (agent lifecycle, threads, sessions)
  • WriteEvent() auto-generates event_id (ULID) and v (version) fields

On initialization:

  1. Opens/migrates SQLite database
  2. Runs JSONL sharding migration (monolithic messages.jsonl -> per-agent files) if needed
  3. Backfills event_id for events that lack it
  4. Creates writers for events.jsonl and lazy-creates per-agent message writers

Exported methods:

  • WriteEvent(event) - Write to JSONL + apply to SQLite projection
  • DB() - Returns SQLite connection for queries
  • RepoID(), RepoPath(), SyncDir() - Path accessors
  • Lock()/Unlock(), RLock()/RUnlock() - Read/write mutex for agent/session operations
  • Projector() - Returns the projection engine
  • Close() - Closes all JSONL writers and SQLite connection

10. Timeout Enforcement (v0.4.3)

All I/O paths enforce timeouts to prevent indefinite hangs:

Path Timeout Implementation
CLI dial 5s net.DialTimeout
RPC call 10s context.WithTimeout
Server per-request 10s http.TimeoutHandler
WebSocket handshake 10s websocket.Upgrader
Git commands 5s/10s safecmd wrapper
SQLite queries context-scoped safedb wrapper

The safedb and safecmd packages wrap all database and command operations with context-aware timeouts. All DB operations go through safedb wrappers and all command executions go through safecmd wrappers for context-aware timeout enforcement.

Lock scope has been reduced in v0.4.3 — no mutex is held during I/O, git, or WebSocket dispatch operations.

11. Monitor Supervisor

Location: internal/daemon/monitor/

The monitor supervisor manages the full lifecycle of monitor jobs — spawning child processes, reading their output, filtering lines against a regex, and delivering matching lines as synthetic Thrum messages.

Key behaviors:

  • Spawns child processes via exec.Command — no shell, ever. The argv is passed directly.
  • Reads stdout and stderr line-by-line. Lines longer than 2KB are truncated with a [truncated] marker.
  • Applies the configured Go RE2 regex to each line. Matching lines are forwarded to the delivery layer.
  • Debounce (leading-edge): the first match in a window delivers immediately. Subsequent matches are suppressed until the window closes. When it closes, a trailing summary is sent if any matches were suppressed. Default window: 60s, minimum: 30s.
  • Synthetic sender format: monitor:<name>. Appears as @monitor:<name> in recipient inboxes.
  • On child exit: sends an exit notification with the last 500 bytes of output and a thrum monitor restart hint. Does not auto-respawn.
  • On daemon restart: loads all monitor specs from the monitors table (schema v20) and respawns each one from scratch.
  • Graceful shutdown: SIGTERM → 5s grace → SIGKILL.
  • Max 100 concurrent monitors. Child processes run with a minimal env whitelist (HOME, USER, LANG, TZ) plus user-supplied --env vars.

Packages:

File Purpose
job.go Monitor job struct, state machine
runner.go Child process spawn, env setup
linereader.go Buffered line reader with 2KB limit
debounce.go Leading-edge debounce with trailing summary
delivery.go Synthetic message delivery to recipient
supervisor.go Lifecycle coordination, DB persistence

See Monitor Jobs for user-facing documentation.

12. Client Library

Location: internal/daemon/client.go

Client library for connecting to the daemon.

Key functions:

  • NewClient(socketPath) - Connect to daemon
  • Call(method, params) - Make RPC call
  • EnsureDaemon(repoPath) - Auto-start daemon if needed

Auto-start logic:

  1. Try to connect to existing daemon
  2. If not running, start daemon in background
  3. Wait for socket to become available (10s timeout)
  4. Return connected client

Daemon Lifecycle

For setup instructions, see Quickstart Guide.

Daemon States

 NOT RUNNING
     │
     ▼
 STARTING ────────┐
     │            │ (error)
     ▼            ▼
 RUNNING ──────▶ ERROR
     │
     ▼
 SHUTTING DOWN
     │
     ▼
 STOPPED

Checking Status

# Check if daemon is running (shows repo path from JSON PID)
thrum daemon status

Stopping the Daemon

# Graceful stop
thrum daemon stop

# Force stop (if graceful fails)
kill <pid>
# flock auto-released by OS; PID file cleaned up on next start

Directory Structure

.git/thrum-sync/a-sync/          # Sync worktree on a-sync orphan branch
├── events.jsonl                # Agent lifecycle events (source of truth)
└── messages/                   # Per-agent message logs (source of truth)
    └── {agent_name}.jsonl

.thrum/                         # Gitignored entirely
├── config.json                 # Daemon configuration (includes identity block)
├── config.json.pre-identity-bak  # One-time backup created on first daemon_id rotation
│                               #   (backup-once: never overwritten after creation)
├── var/                        # Runtime files
│   ├── thrum.sock              # Unix socket for CLI/RPC
│   ├── thrum.pid               # JSON PID file (PID, RepoPath, StartedAt, SocketPath)
│   ├── thrum.lock              # flock file for SIGKILL resilience
│   ├── ws.port                 # WebSocket port number
│   ├── sync.lock               # Sync loop file lock
│   └── messages.db             # SQLite projection (query cache)
├── identities/                 # Per-worktree agent identity files
│   └── {agent_name}.json
└── redirect                    # (feature worktrees only) points to main .thrum/

Key files:

  • .git/thrum-sync/a-sync/events.jsonl and messages/*.jsonl: Append-only JSONL event logs on the a-sync branch, synced via the worktree. Per-agent sharding means each agent's messages go to a separate JSONL file.
  • var/messages.db: SQLite database rebuilt from JSONL, stores all queryable state including agent_work_contexts table
  • var/thrum.pid: JSON format with PID, repo path, start time, and socket path. Enables repo-affinity checks to prevent conflicts.
  • var/thrum.lock: Held via flock() for the daemon's lifetime. OS releases on process death (even SIGKILL).
  • var/ws.port: Contains the WebSocket port for UI clients to discover
  • redirect: Present only in feature worktrees; points to the main worktree's .thrum/ so all worktrees share one daemon

Development

Running Tests

# All daemon tests
go test ./internal/daemon/...

# With coverage
go test -cover ./internal/daemon/...

# With race detector
go test -race ./internal/daemon/...

Adding New RPC Methods

  1. Create handler in internal/daemon/rpc/
  2. Implement Handle(ctx, params) method
  3. Register in daemon startup (cmd/thrum/main.go)
  4. Add tests in corresponding _test.go file

Example:

// internal/daemon/rpc/mymethod.go
type MyMethodHandler struct {
    // dependencies
}

func (h *MyMethodHandler) Handle(ctx context.Context, params json.RawMessage) (any, error) {
    // implementation
    return response, nil
}

// Register in daemon (cmd/thrum/main.go)
server.RegisterHandler("mymethod", myMethodHandler.Handle)

Troubleshooting

Daemon won't start

Check:

  1. Is .thrum/var/ directory writable?
  2. Is socket path too long? (Unix sockets have 104-char limit)
  3. Is another daemon already running?
# Check JSON PID file
cat .thrum/var/thrum.pid

# Check if flock is held
# (if daemon died via SIGKILL, flock is released but PID file may remain)

# Check if process is running
ps aux | grep thrum

# Remove stale PID file (only if process is definitely not running)
rm .thrum/var/thrum.pid

Socket connection errors

Check:

  1. Is daemon running?
  2. Socket permissions correct (should be 0600)
  3. Socket file exists?
# Check socket
ls -l .thrum/var/thrum.sock

# Test connection
echo '{"jsonrpc":"2.0","method":"health","id":1}' | nc -U .thrum/var/thrum.sock

Graceful shutdown hangs

The daemon waits up to 5 seconds for in-flight requests to complete. If shutdown hangs longer:

# Send SIGKILL (flock auto-released by OS)
kill -9 <pid>

# Stale files are cleaned up automatically on next daemon start
# Manual cleanup if needed:
rm .thrum/var/thrum.sock
rm .thrum/var/thrum.pid

Implemented Features

Epic Feature Status
Epic 2 Daemon core (Unix socket, JSON-RPC) Complete
Epic 3 Agent & session management Complete
Epic 4 Message send/receive Complete
Epic 5 Git sync protocol Complete
Epic 6 Subscription & notifications Complete
Epic 8 WebSocket server Complete
Epic F Embedded SPA (single port) Complete
Epic 21 Agent Work Context (live git state) Complete
DLH Daemon Lifecycle Hardening (flock, JSON PID, defer cleanup) Complete
MCP MCP Server (thrum mcp serve) Complete
JSONL Sharding Per-agent JSONL files, event_id, naming, cleanup Complete
Sync WT Sync worktree at .git/thrum-sync/a-sync/ Complete
Agent Naming Human-readable agent names (--name, THRUM_NAME) Complete
Agent Cleanup agent.delete, agent.cleanup (orphan detection) Complete
Browser Auth Browser auto-registration via git config Complete
Local-Only Mode Disable remote sync for public repos Complete
Backup/Restore JSONL export, SQLite snapshot, GFS rotation, plugin hooks Complete
Monitor Jobs v1 Process watcher, regex filter, debounce, synthetic delivery Complete

Next Steps

  • Architecture — the full system architecture showing how daemon, CLI, MCP server, and Web UI fit together
  • RPC API Reference — every RPC method the daemon exposes over Unix socket and WebSocket
  • Sync Protocol — how the daemon's sync loop fetches, merges, and pushes the a-sync branch
  • Development Guide — how to add new RPC handlers and run the daemon test suite