How the daemon process is started, monitored, and stopped by the desktop app and IDE extension
Lifecycle
The daemon is a long-running Node.js process managed by the clients that use it. Both the desktop app and IDE extension can start the daemon, and it persists independently of either.
Process management
PID and port files
The daemon writes two sentinel files on startup:
~/.codmir/kernel.pid— the process ID~/.codmir/kernel.port— the port number (default 7700)
These files are the coordination mechanism. Any client can check if the daemon is running by:
- Reading the PID from
~/.codmir/kernel.pid - Checking if that process is alive (
kill(pid, 0)on Unix) - If alive, reading the port from
~/.codmir/kernel.portand connecting
Startup sequence
When the daemon starts (startDaemon() in packages/kernel/src/daemon.ts):
- Check for existing instance — if
kernel.pidexists and the process is alive, exit immediately - Redirect stdout/stderr to
~/.codmir/logs/kernel.log - Create MeshManager (wraps ProcessManager + mesh networking)
- Create KernelSocketServer and bind to port
- If
ANTHROPIC_API_KEYis set, create OverseerLoop + WakeBridge - Start the mesh manager and socket server
- Write PID and port files
- Start PlatformPresence (cloud heartbeat)
- Register SIGINT/SIGTERM handlers for graceful shutdown
Graceful shutdown
On SIGINT or SIGTERM:
- Stop PlatformPresence (deregister from cloud)
- Stop WakeBridge and OverseerLoop
- Stop MeshManager (cancel queued tasks, close peer connections)
- Close KernelSocketServer (drop all WebSocket connections)
- Remove
kernel.pidandkernel.portfiles
Desktop app (Tauri)
The desktop app manages the daemon through Tauri's Rust backend.
ensure_daemon
The primary Tauri command. Called on app startup via invoke('ensure_daemon'):
// apps/desktop/src-tauri/src/daemon.rs
pub async fn do_start(app: &AppHandle) -> Result<DaemonState, String> {
// 1. Check if already running via PID file
// 2. Find the daemon binary (daemon-entry.js)
// 3. Spawn as a detached process with Stdio::null()
// 4. Poll PID file for up to 8 seconds (250ms intervals)
// 5. Return DaemonState { status: Online, pid, port }
}Binary lookup order:
~/.codmir/node_modules/@codmir/kernel/dist/daemon-entry.js/usr/local/lib/node_modules/@codmir/kernel/dist/daemon-entry.jswhich codmir-daemon(PATH lookup)
Background monitor
A Tokio task polls daemon status every 5 seconds:
pub fn start_daemon_monitor(app: AppHandle) {
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
let new_status = check_daemon_status().await;
if new_status != last_status {
app.emit("daemon-status-changed", &state);
update_tray(&app, &state);
}
}
});
}The monitor:
- Updates the system tray icon (green dot = online, red = offline)
- Emits a Tauri event so the React frontend can react to status changes
- Auto-restarts the daemon if it crashes (configurable)
React integration
// apps/desktop/src/hooks/use-kernel.ts
export function useKernel() {
useEffect(() => {
useKernelStore.getState().ensureAndConnect();
return () => useKernelStore.getState().disconnect();
}, []);
}
// apps/desktop/src/stores/kernel-store.ts
ensureAndConnect: async () => {
const info = await invoke<DaemonInfo>('ensure_daemon');
get().connect(info.port);
}Tauri commands
| Command | Description |
|---|---|
ensure_daemon | Start if not running, return connection info |
daemon_status | Return current DaemonState |
daemon_start | Force start (even if already running) |
daemon_stop | Stop the daemon gracefully |
daemon_restart | Stop then start |
daemon_logs | Return last 200 lines of ~/.codmir/logs/kernel.log |
daemon_install | Install the kernel package globally |
IDE extension (VS Code)
The VS Code extension manages the daemon through its KernelClientService.
Connection flow
// apps/vscode-extension/src/extension/services/KernelClientService.ts
async connect() {
const port = await this._discoverPort();
if (!port) {
await this._ensureDaemon();
port = await this._discoverPort();
}
this._connectWs(port);
}Port discovery
_discoverPort(): number | null {
// 1. Read ~/.codmir/kernel.port
// 2. Read ~/.codmir/kernel.pid
// 3. Verify process is alive: process.kill(pid, 0)
// 4. Return port if alive, null otherwise
}Daemon startup from extension
If the daemon isn't running, the extension forks it:
_ensureDaemon() {
const entry = this._findDaemonEntry();
const child = fork(entry, ['start', '--foreground'], {
detached: true,
stdio: 'ignore',
env: { ...process.env, KERNEL_PORT: '7700' },
});
child.unref();
// Poll PID file for up to 5 seconds
}Binary lookup:
~/.codmir/node_modules/@codmir/kernel/dist/daemon-entry.jsrequire.resolve('@codmir/kernel/daemon-entry')- Relative paths from the extension's installation directory
Webview bridge
The extension acts as a bridge between the webview UI and the daemon:
Webview (React) ←→ Extension Host ←→ Daemon (WebSocket)
postMessage MessageBridge KernelClientService- Webview sends
START_KERNEL_SESSION→ extension callskernelClient.startSession() - Daemon sends events → extension forwards via
bridge.send({ type: 'KERNEL_SESSION_EVENT', payload })→ webview receives viaonDidReceiveMessage
Platform compatibility
macOS / Linux
- Process spawning:
fork()/detached: true/child.unref() - Signal handling: SIGTERM for graceful stop, SIGKILL as fallback
- PID files:
~/.codmir/kernel.pid - Works identically on both platforms
Windows (planned)
- Process spawning: Same
detached: true+unref()pattern works - Signal handling: No SIGTERM — use
taskkill /PID {pid}instead - PID files:
%USERPROFILE%\.codmir\kernel.pid - WebSocket: Works identically
Environment variables
| Variable | Default | Description |
|---|---|---|
KERNEL_PORT | 7700 | Port for the WebSocket server |
ANTHROPIC_API_KEY | — | Required for agent sessions and overseer |
CODMIR_PROXY_URL | — | Route API calls through Codmir's proxy |
NODE_ENV | — | Set to production for log reduction |
Troubleshooting
Daemon won't start
# Check if already running
cat ~/.codmir/kernel.pid && kill -0 $(cat ~/.codmir/kernel.pid)
# Check logs
tail -50 ~/.codmir/logs/kernel.log
# Remove stale PID file if process is dead
rm ~/.codmir/kernel.pid ~/.codmir/kernel.portPort conflict
If port 7700 is in use:
KERNEL_PORT=7701 codmir daemon startConnection issues
Both clients look for the daemon at ws://127.0.0.1:{port}. If a firewall blocks localhost WebSocket connections, the daemon won't be reachable.
The daemon binds to 0.0.0.0 (all interfaces) to support mesh networking, but clients always connect via 127.0.0.1.