# Ticket 3.2 — IPC Protocol

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

## Description

JSON-lines protocol over Unix socket for communication between the CLI process and the daemon. Each command is a single JSON line, and each response is a single JSON line. This enables the CLI to be stateless — it connects, sends a command, reads the response, and disconnects.

## Acceptance Criteria

### AC1: Unix socket creation and cleanup

```gherkin
Given the daemon starts
When it initialises the IPC listener
Then it creates a Unix socket at ~/.conduit/relay.sock
And it sets permissions to owner-only (0600)
And on shutdown, it removes the socket file
```

### AC2: Command/response protocol

```gherkin
Given the CLI connects to the Unix socket
When it sends a JSON line: {"cmd": "get_status"}
Then the daemon responds with a JSON line: {"ok": true, "uptime": ..., "port": ..., "projects": [...]}
And the connection can be closed after the response
```

### AC3: Project management commands

```gherkin
Given the daemon is running
When CLI sends {"cmd": "add_project", "directory": "/path/to/project"}
Then daemon responds {"ok": true, "slug": "project"}

When CLI sends {"cmd": "remove_project", "slug": "project"}
Then daemon responds {"ok": true}

When CLI sends {"cmd": "list_projects"}
Then daemon responds {"ok": true, "projects": [{"slug": "...", "directory": "...", "title": "..."}]}

When CLI sends {"cmd": "set_project_title", "slug": "project", "title": "My App"}
Then daemon responds {"ok": true}
```

### AC4: Configuration commands

```gherkin
When CLI sends {"cmd": "set_pin", "pin": "1234"}
Then daemon responds {"ok": true}

When CLI sends {"cmd": "set_keep_awake", "enabled": true}
Then daemon responds {"ok": true}

When CLI sends {"cmd": "shutdown"}
Then daemon begins graceful shutdown and responds {"ok": true}
```

### AC5: OpenCode-specific commands

```gherkin
When CLI sends {"cmd": "set_agent", "slug": "project", "agent": "plan"}
Then daemon responds {"ok": true}

When CLI sends {"cmd": "set_model", "slug": "project", "provider": "anthropic", "model": "claude-sonnet-4-20250514"}
Then daemon responds {"ok": true}
```

### AC6: Error responses

```gherkin
Given an invalid command
When CLI sends {"cmd": "nonexistent"}
Then daemon responds {"ok": false, "error": "Unknown command: nonexistent"}

Given project slug doesn't exist
When CLI sends {"cmd": "remove_project", "slug": "nope"}
Then daemon responds {"ok": false, "error": "Project not found: nope"}
```

### AC7: Concurrent connections

```gherkin
Given two CLI processes connect simultaneously
When both send commands
Then both receive correct responses
And commands are processed sequentially (no interleaving)
```

## Testable Components

| Component | Test Type | What to Assert |
|-----------|-----------|----------------|
| Socket creation | Unit | Created at correct path, correct permissions |
| Socket cleanup | Unit | Removed on shutdown |
| `handleCommand(cmd)` | Unit | Routes to correct handler, returns response |
| `add_project` | Unit | Validates directory, generates slug, returns it |
| `remove_project` | Unit | Finds by slug, removes, returns ok |
| `list_projects` | Unit | Returns all projects with metadata |
| `set_pin` | Unit | Validates format, updates config |
| `shutdown` | Unit | Triggers graceful shutdown |
| `set_agent` / `set_model` | Unit | Forwards to correct project |
| Error handling | Unit | Returns structured error for each failure case |
| Concurrent connections | Fixture | Two simultaneous clients get correct responses |
| Full CLI→daemon flow | Fixture | CLI connects, sends command, receives response |

## API Shape

```ts
// src/lib/ipc.ts
export class IPCServer {
  constructor(socketPath, commandHandler)
  async start()
  async stop()
}

export class IPCClient {
  constructor(socketPath)
  async send(command)     // Returns response object
  async isAlive()         // Quick health check
}

// Command handler signature:
// async function handleCommand(cmd) → { ok: true, ...data } | { ok: false, error: "..." }
```

## Reference

- Claude-relay IPC: `../claude-relay/lib/ipc.js`
- Socket path: `~/.conduit/relay.sock`
- JSON-lines: one JSON object per line, newline-delimited
