How AgentsView Daemon Mode Works for Background Serving

AgentsView daemon mode launches a long-running HTTP server that exclusively locks the SQLite archive, writes a runtime discovery record, and optionally shuts down automatically after idle periods.

AgentsView implements a robust daemon mode for background serving that transforms the CLI into a persistent HTTP API server. When you execute agentsview serve, the application transitions from a short-lived command to a background process that maintains exclusive ownership of the database while exposing REST endpoints for other CLI commands and external integrations.

Startup Lock and Exclusive Database Ownership

The daemon initialization begins with an exclusive advisory lock to prevent database corruption. In cmd/agentsview/daemon_runtime.go, the function MarkDaemonStarting (also referenced as markDaemonStarting) creates an advisory lock file named runtime.lock in the data directory immediately after the CLI parses the configuration.

This lock guarantees that only a single process may start the server at any given time. If another process attempts to start while the lock is held, the operation fails. The daemon also implements rejectLiveWritableDaemonBeforeDirectWrite to block other processes from opening the SQLite archive for writes while the daemon is active, ensuring data consistency.

Runtime Record and Service Discovery

After the HTTP listener is created via daemon.Listen, the daemon writes a Kit daemon runtime record using WriteDaemonRuntime in cmd/agentsview/daemon_runtime.go. This record contains critical metadata:

  • Host and port of the listening interface
  • Version string for compatibility checking
  • Read-only flag indicating if mutating operations are allowed
  • Optional Caddy PID for reverse proxy management

Other CLI commands locate the daemon by calling FindDaemonRuntime, which scans the data directory for this record and probes the stored endpoint. This mechanism enables commands like agentsview stats and agentsview sync to discover and communicate with the background server without hardcoded network addresses.

HTTP Server and Listener Architecture

The server initialization flow in internal/server/server.go coordinates the network stack. The Server.ListenAndServe method builds a TCP address from the configuration and delegates to daemon.Listen from the go.kenn.io/kit/daemon package, which supplies the actual listener and manages low-level socket options.

Once running, the daemon serves the Single Page Application (SPA) and API endpoints at /api/v1/.... The server maintains a mutex-protected reference to the HTTP server instance (s.httpSrv) to enable safe shutdown operations.

Idle Timeout and Automatic Shutdown

When the daemon launches as a background child process (runningAsBackgroundChild), the system creates an IdleTracker configured via daemonIdleTimeout. Implemented in internal/server/idle.go, this tracker monitors HTTP request activity.

If no requests occur for the configured duration, the tracker invokes stop(), which triggers a graceful shutdown. This feature prevents resource exhaustion from forgotten background processes while allowing the daemon to persist during active use periods.

Graceful Shutdown Process

The shutdown sequence in internal/server/server.go ensures clean resource release. The Server.Shutdown method:

  1. Stops the HTTP server using http.Server.Shutdown(ctx), which stops accepting new connections while allowing existing requests to complete
  2. Closes any on-demand sync engine instances
  3. Releases the startup lock

After the server returns, the runtime record is removed via RemoveDaemonRuntime in cmd/agentsview/daemon_runtime.go, signaling to other processes that the daemon is no longer available.

Read-Only Mode and Safety Features

The daemon supports a read-only mode activated via --no-sync or when running as a PG "push" server. In this configuration, the runtime record's read_only flag is set to true, and the HTTP backend in internal/service/http.go returns 501 Not Implemented for mutating endpoints.

This safety feature makes the daemon suitable for low-privilege clients that should only query data without modifying the SQLite archive. The system also implements automatic restart detection: when a daemon crashes, its lock disappears, allowing subsequent CLI invocations to start a fresh daemon and write a new runtime record.

Practical Usage Examples

Start the daemon in the foreground, replacing any existing instance:

agentsview serve --replace

Start the daemon as a background process:

agentsview serve --replace &

Check daemon status using the runtime record:

agentsview serve status

# → "agentsview daemon is active at http://127.0.0.1:8080"

Stop a running daemon gracefully:

agentsview serve stop

Discover the daemon endpoint from Go code:

import "go.kenn.io/agentsview/internal/daemon"

func getDaemonURL(dataDir string) (string, error) {
    rt := daemon.FindDaemonRuntime(dataDir)
    if rt == nil {
        return "", fmt.Errorf("no daemon found")
    }
    return fmt.Sprintf("http://%s:%d", rt.Host, rt.Port), nil
}

Implement graceful shutdown in extensions:

func (s *Server) Shutdown(ctx context.Context) error {
    s.mu.RLock()
    srv := s.httpSrv
    s.mu.RUnlock()
    if srv != nil {
        _ = srv.Shutdown(ctx) // stops accepting new connections
    }
    // Runtime record removal handled by caller
    return nil
}

Summary

  • Exclusive locking via MarkDaemonStarting and runtime.lock prevents multiple daemon instances from corrupting the SQLite archive.
  • Runtime records written by WriteDaemonRuntime enable automatic discovery via FindDaemonRuntime, allowing other CLI commands to locate the HTTP API.
  • Background operation supports detached execution with automatic idle timeout reaping configured through newDaemonIdleTracker.
  • Graceful shutdown in Server.Shutdown closes the HTTP server, sync engines, and removes the runtime record without dropping active connections.
  • Read-only safety returns HTTP 501 for write operations when the daemon starts with --no-sync, protecting data from unauthorized modifications.

Frequently Asked Questions

How does AgentsView prevent multiple daemon instances from running simultaneously?

The daemon uses an advisory lock file mechanism implemented in cmd/agentsview/daemon_runtime.go. When starting, MarkDaemonStarting creates a runtime.lock file in the data directory. If another process attempts to start while this lock exists, the operation fails. Additionally, the rejectLiveWritableDaemonBeforeDirectWrite check prevents other processes from opening the SQLite database for writes while the daemon holds the lock.

What triggers the automatic shutdown of a background daemon?

When running as a background child process, the daemon creates an IdleTracker (defined in internal/server/idle.go) that monitors HTTP request activity. If no requests arrive for the duration specified by daemonIdleTimeout, the tracker calls stop(), initiating a graceful shutdown via Server.Shutdown. This prevents orphaned processes from consuming system resources indefinitely.

How do other CLI commands communicate with the running daemon?

Commands like agentsview stats and agentsview sync call FindDaemonRuntime from cmd/agentsview/daemon_runtime.go to scan the data directory for the runtime record. This JSON file contains the host, port, and version information needed to construct the API endpoint URL. The command then issues HTTP requests to /api/v1/... endpoints, with the client wrapper in internal/service/http.go handling the transport layer.

What is the difference between regular and read-only daemon mode?

In read-only mode (activated by --no-sync or PG push server configuration), the daemon sets the read_only flag in its runtime record. The HTTP handler in internal/service/http.go checks this flag and returns HTTP 501 for any mutating requests. This allows the daemon to safely serve data to low-privilege clients while preventing modifications to the SQLite archive, whereas regular mode permits full CRUD operations through the API.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →