# Ticket 3.1 — Daemon Process

**Phase**: 3 — Daemon & CLI
**Priority**: Critical (blocking)
**Depends on**: 2.1

## Description

Background daemon that persists across terminal sessions. The daemon owns the HTTP server, manages multiple projects, and communicates with the CLI via a Unix socket. When a user runs `conduit` in a project directory, it connects to the daemon (starting it if needed) and registers the project.

## Acceptance Criteria

### AC1: Daemon starts as detached background process

```gherkin
Given no daemon is currently running
When the CLI starts the daemon
Then it spawns a detached child process running daemon.ts
And the child process survives the parent terminal closing
And it writes a PID file to ~/.conduit/daemon.pid
And it writes logs to ~/.conduit/daemon.log
```

### AC2: Daemon creates HTTP server

```gherkin
Given the daemon process starts
When it initialises
Then it starts the relay HTTP/HTTPS server (ticket 2.1)
And it starts listening on the configured port
And it creates the Unix socket for IPC
```

### AC3: Health check detects running daemon

```gherkin
Given the daemon is running with Unix socket at ~/.conduit/relay.sock
When the CLI sends a health check via IPC
Then it receives a response within 1 second
And the response includes: { ok: true, uptime, port, projectCount }
```

### AC4: Stale daemon detection

```gherkin
Given a daemon PID file exists but the process has crashed
When the CLI tries to connect
Then it detects the stale socket (connection refused or timeout)
And it removes the stale PID file and socket
And it starts a fresh daemon
```

### AC5: Graceful shutdown

```gherkin
Given the daemon is running with 2 projects
When it receives a shutdown command via IPC
Then it stops all OpenCode child processes
And it closes all WebSocket connections
And it closes the HTTP server
And it removes the Unix socket and PID file
And the process exits with code 0
```

### AC6: Signal handling

```gherkin
Given the daemon is running
When it receives SIGTERM
Then it performs graceful shutdown

When it receives SIGINT
Then it performs graceful shutdown

When it receives SIGHUP
Then it reloads configuration without stopping
```

### AC7: Daemon auto-restart tracking

```gherkin
Given the daemon starts
When it has been running successfully for more than 60 seconds
Then it resets the crash counter

Given the daemon crashes and restarts 3 times within 60 seconds
Then it logs an error and does NOT attempt another restart
And it leaves the PID file with a "failed" marker
```

## Testable Components

| Component | Test Type | What to Assert |
|-----------|-----------|----------------|
| `spawn()` | Unit | Detached, correct script path, stdio config |
| `healthCheck()` | Unit | Returns status on success, detects stale on failure |
| `staleDetection()` | Unit | Removes stale PID/socket, returns false for health |
| `shutdown()` | Unit | Stops projects, closes server, removes files |
| Signal handling | Unit | SIGTERM/SIGINT trigger shutdown, SIGHUP reloads |
| PID file management | Unit | Written on start, removed on stop |
| Full lifecycle | Fixture | Start → health check → add project → shutdown |

## API Shape

```ts
// src/lib/daemon.ts
export class Daemon {
  constructor(options = {})        // { port, configDir, socketPath }
  async start()                    // Start server, IPC socket
  async stop()                     // Graceful shutdown
  async addProject(directory)      // Register a project
  async removeProject(slug)        // Unregister a project
  getStatus()                      // { uptime, port, projects, clients }
  static async isRunning(socketPath)  // Check if daemon is alive
  static async spawn(options)         // Start daemon as background process
}
```

## Reference

- Claude-relay daemon: `../claude-relay/lib/daemon.js`
- Unix socket path: `~/.conduit/relay.sock`
- PID file: `~/.conduit/daemon.pid`
- Log file: `~/.conduit/daemon.log`
