# Cairn — Network

## Overview

Three communication paths:

```
┌──────────┐  HTTP API   ┌──────────────┐  Docker SDK  ┌──────────────┐
│  Client  │ ──────────→ │ Cairn Server │ ←─────────── │  Dispatcher  │
│(browser/ │             │  (FastAPI)   │              │              │
│ curl/    │             │  port 8000   │              │  ThreadPool  │
│ worker)  │             └──────────────┘              └──────┬───────┘
└──────────┘                                                 │
                                                    Docker Container
                                                    ┌──────────────┐
                                                    │    Worker    │
                                                    │ (claude/codex│
                                                    │  /pi/mock)   │
                                                    └──────────────┘
```

## Protocol Details

### 1. Client → Server (HTTP/JSON)

| Direction | Protocol | Format | Auth |
|-----------|----------|--------|------|
| Inbound | HTTP/1.1 | JSON (Pydantic models) | None |
| Export endpoint | HTTP/1.1 | text/plain (YAML or timeline) | None |

FastAPI auto-generates OpenAPI spec at `/docs` (Swagger UI) and `/openapi.json`.

### 2. Dispatcher → Server (HTTP/JSON)

`CairnClient` (`dispatcher/protocol/client.py`) wraps `requests.Session`:

| Method | Endpoint | Purpose |
|--------|----------|---------|
| GET | `/projects` | List all projects |
| GET | `/projects/{id}` | Get project detail |
| GET | `/projects/{id}/export` | Get graph YAML |
| GET | `/settings` | Validate timeout config |
| POST | `/projects/{id}/intents` | Create bootstrap intent |
| POST | `/projects/{id}/intents/{id}/heartbeat` | Claim/renew intent lease |
| POST | `/projects/{id}/intents/{id}/release` | Release intent |
| POST | `/projects/{id}/intents/{id}/conclude` | Complete intent with fact |
| POST | `/projects/{id}/reason/claim` | Claim reason slot |
| POST | `/projects/{id}/reason/heartbeat` | Renew reason lease |
| POST | `/projects/{id}/reason/release` | Release reason slot |
| POST | `/projects/{id}/complete` | Mark project complete |

### 3. Dispatcher → Worker Container (Docker SDK + Subprocess)

| Component | Technology | Purpose |
|-----------|-----------|---------|
| Container creation | Docker SDK (`docker` Python package) | Create/manage per-project containers |
| Command execution | `subprocess.Popen` via `process.py` | Run worker CLI commands inside container |
| Heartbeat | Background thread in `heartbeat.py` | Send heartbeat to server during task execution |
| Network mode | Configurable in `dispatch.yaml` | `container.network_mode` (e.g., `host`) |

Worker containers receive:
- Prompt template files (mounted as volume)
- Environment variables (from config + common_env)
- Network access (for pentest tools, configurable mode)

### 4. Worker → LLM (varies by type)

| Worker | Transport | Endpoint |
|--------|-----------|----------|
| ClaudeCode | CLI subprocess | Anthropic API (via `claude` CLI) |
| Codex | CLI subprocess | OpenAI API (via `codex` CLI) |
| Pi | HTTP (via `_curl.py`) | Pi API (configurable base URL) |
| Mock | In-process | Probabilistic outcome selection |

## Lease Mechanism

All claims use heartbeat-based leases:

```
Worker claims intent → server sets worker + last_heartbeat_at
Worker sends heartbeat every interval → server updates last_heartbeat_at
Server expires lease if last_heartbeat_at < now - timeout
Worker releases intent → server clears worker field
```

| Lease type | Timeout config | Default |
|-----------|---------------|---------|
| Intent lease | `intent_timeout` (server settings) | 15s |
| Reason lease | `reason_timeout` (server settings) | 15s |
| Dispatcher interval | `runtime.interval` (dispatch.yaml) | Must be < timeout |

## Security

- No authentication on server API
- No TLS (plain HTTP)
- Worker containers get `cap_add` capabilities from config (e.g., `NET_RAW` for ping)
- Network mode configurable per deployment (default: bridge, can be host for pentest tools)
