# Dialetica AI — Self-hosted server

Run Dialetica's multi-agent platform on your own machine. Email + password auth, embedded Postgres, no external service dependencies for the core flow.

> Status: alpha. Expect rough edges. File issues at <https://github.com/enzobjmendonca/dialectica-ai/issues>.

---

## Quick start

Requires:

- **Node.js 22 or newer**
- **Docker** — Docker Desktop on macOS/Windows, or the docker engine on Linux. Every agent turn runs inside a per-workspace sandbox container; the first turn pulls `ghcr.io/enzobjmendonca/dialectica-ai/openclaw-sandbox:<version>` (~300 MB) which is then cached.

```bash
npm install -g @dialetica/server
dialetica doctor              # verifies Node, Docker, ports
dialetica init                # creates ~/.dialetica/config.env
# edit ~/.dialetica/config.env to add at least one LLM API key
dialetica start               # boots gateway + frontend
```

Open <http://localhost:3000>. Register the first account — that user becomes the platform admin automatically.

---

## Commands

### `dialetica init`

Creates `~/.dialetica/config.env` with a freshly-generated `JWT_SECRET` and slots for the API keys you'll edit by hand.

```
dialetica init           # writes config.env (refuses to overwrite)
dialetica init --force   # overwrite an existing config — invalidates every session
```

`--force` invalidates all issued session tokens. Only use it for a deliberate key rotation.

### `dialetica start`

Boots the gateway (port 8000) and the Next.js frontend (port 3000) as child processes.

```
dialetica start
dialetica start --gateway-port 9000 --frontend-port 4000
```

`SIGINT` / `SIGTERM` on the parent gracefully terminates both children. An unexpected child exit tears down its sibling so a supervisor (systemd, your terminal) sees a clean failure rather than a half-running deployment.

### `dialetica doctor`

Pre-flight diagnostics. Exits non-zero if anything's wrong.

```
dialetica doctor
```

Checks:

- Node version (≥22)
- `~/.dialetica/config.env` presence and `JWT_SECRET` length
- At least one LLM API key
- Gateway + frontend build artifacts
- Port 8000 / 3000 availability
- Docker daemon presence + responsiveness (fail — required for every agent turn)
- Data directory presence

### `dialetica help` / `version`

Self-explanatory.

---

## Configuration

`~/.dialetica/config.env` is loaded once at `dialetica start` and merged into both child processes' environments. Format is a strict subset of dotenv: `KEY=value`, `KEY="quoted value"`, `# comment`, blank lines. No variable expansion. Set ports / paths on the command line or in your shell env to override.

### Required

| Key          | What it does                                                                 |
|--------------|------------------------------------------------------------------------------|
| `JWT_SECRET` | Signs the session JWTs. Generated by `dialetica init`. Changing it invalidates all sessions. |

### LLM keys — supply at least one

| Key                  | When you need it                                  |
|----------------------|---------------------------------------------------|
| `OPENAI_API_KEY`     | Agents configured to use OpenAI models.           |
| `ANTHROPIC_API_KEY`  | Agents configured to use Claude models.           |
| `GOOGLE_API_KEY`     | Agents configured to use Gemini models.           |
| `AI_GATEWAY_API_KEY` | Optional. Routes via Vercel AI Gateway when set.  |

An agent fails its first turn if it's configured for a model whose key isn't present.

### Optional

| Key                | What it does                                                                 |
|--------------------|------------------------------------------------------------------------------|
| `SMTP_URL`         | Password-reset emails go through this transport. Leave blank to disable email delivery — the reset URL falls back to the operator log so an admin can hand it off. |
| `PORT`             | Gateway port (default 8000).                                                 |
| `WORKSPACE_DIR`    | Agent workspace files. Default `~/.dialetica/workspace`.                     |
| `DIALETICA_FILES_ROOT` | Attachment storage. Default `~/.dialetica/files`.                        |
| `OPENCLAW_STATE_DIR` | OpenClaw runtime state. Default `~/.dialetica/openclaw`.                   |

LLM provider keys, channel bot tokens (`TELEGRAM_BOT_TOKEN`, `DISCORD_BOT_TOKEN`, Slack env-var mode), and per-deployment paths all live at this top level — they work in self-hosted mode when you supply them.

---

## Sandbox container

Docker is required. Every agent turn runs inside a per-workspace container based on `openclaw-sandbox:<version>` — the agent's filesystem and command-exec tools never touch the host. The first turn pulls the image (~300 MB) from ghcr.io and caches it; subsequent turns reuse the running container until it's idle for ~24h.

If Docker isn't installed or the daemon isn't running, `dialetica doctor` exits non-zero and agents will refuse to take a turn. Install Docker Desktop (macOS/Windows) or your distro's docker package (Linux) before `dialetica start`.

The browser tool spawns a second container (`openclaw-sandbox-browser:<version>`, ~1.6 GB, lazy — only pulled when an agent first calls a browser tool).

---

## Data location

Everything lives under `~/.dialetica/`:

```
~/.dialetica/
├── config.env       Generated by `dialetica init`. Edit by hand.
├── data/            PGlite database (embedded Postgres). Don't edit.
├── workspace/       Per-agent workspace files.
├── files/           Content-addressed attachment storage.
└── openclaw/        OpenClaw runtime state (per-org agent configs).
```

Backing up Dialetica = `tar` of `~/.dialetica/`.

---

## Self-hosted vs platform mode

`dialetica` always runs in self-hosted mode:

- Embedded PGlite database — no external Postgres required.
- Email + password auth only — no Google OAuth (set up your own auth provider proxy if you need it).
- No managed integrations (no Composio platform key, no Zavu cloud WhatsApp, no SES for emails).
- LLM provider keys are yours; Dialetica never proxies them.

Dialetica's hosted product (<https://dialetica.ai>) runs the same codebase in "platform mode" with managed integrations and Google OAuth. None of that infrastructure ships in this package.

---

## Going to production (behind a reverse proxy)

For exposing Dialetica to the internet, run it behind a TLS-terminating reverse proxy (Caddy, nginx, Traefik). You only need to forward one port: **`:3000`** (the frontend). The frontend's built-in proxy (`/api/foundation/*`) talks to the gateway over loopback — no path-based fan-out, no separate API subdomain, no rebuild.

### Caddy (single-domain shape — recommended)

```caddyfile
chat.example.com {
    reverse_proxy localhost:3000
}
```

That's it. Caddy auto-acquires TLS from Let's Encrypt and transparently passes WebSocket upgrades through.

### nginx equivalent

```nginx
server {
    listen 443 ssl http2;
    server_name chat.example.com;
    # …TLS cert config…

    location / {
        proxy_pass         http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400s;  # WebSocket idle
    }
}
```

The `Upgrade`/`Connection` headers and the long `proxy_read_timeout` matter — the frontend's `/api/foundation/v1/*` carries WebSocket traffic that the proxy must pass through. Caddy handles this automatically; nginx needs it spelled out. `X-Forwarded-Proto` lets the frontend pick the right NextAuth cookie name (`__Secure-` prefix) when TLS terminates upstream.

### systemd unit

```ini
[Unit]
Description=Dialetica AI
After=network.target docker.service

[Service]
Type=simple
WorkingDirectory=/opt/dialetica
EnvironmentFile=/opt/dialetica/.env
ExecStart=/usr/bin/dialetica start
Restart=always
RestartSec=5
Environment=NODE_ENV=production
User=ubuntu

[Install]
WantedBy=multi-user.target
```

The `.env` should contain whatever's in `~/.dialetica/config.env` plus anything systemd needs to find (LLM keys, channel tokens, `AUTH_SECRET` for cookie-decrypt in the WS proxy).

### Updates

```bash
sudo npm install -g @dialetica/server@latest
sudo systemctl restart dialetica
```

Versions on the `next` dist-tag (`@next`) are canaries — newer, less soaked. Stick to `@latest` unless you're tracking the bleeding edge.

---

## Troubleshooting

**`dialetica start` says "frontend entry not found".** The npm package may have shipped without the frontend tree. Reinstall: `npm install -g @dialetica/server@latest`. If the problem persists, file an issue with the output of `dialetica doctor`.

**Port 8000 / 3000 is already in use.** Override: `dialetica start --gateway-port 9000 --frontend-port 4000`. The frontend talks to the gateway via the proxy so it auto-discovers the gateway port from its env.

**Password reset email never arrives.** Without `SMTP_URL` set, password-reset URLs go to the operator log. Look in the `dialetica start` stdout for a log line like `password reset link (operator hand-off)` and hand the URL to the user directly. To enable email delivery, set `SMTP_URL=smtps://user:pass@host:port` in `config.env`.

**Lost the admin account.** PGlite stores everything in `~/.dialetica/data/`. Reset the whole install with `rm -rf ~/.dialetica && dialetica init` (loses all data) or set a known password directly in the DB if you're comfortable with `psql`-style tools against PGlite.

**Node 22 isn't available.** Use nvm: `nvm install 22 && nvm use 22`.

---

## License

See `LICENSE` in this package.
