> ## Documentation Index
> Fetch the complete documentation index at: https://solo-09d10f60.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> A conservative two-machine deployment guide for the Enoch Control Plane and worker gate.

# Deployment

The reference deployment splits Enoch across two machines:

* **Control VM** — runs the FastAPI control plane, the professional operator dashboard, bounded read models, publication automation APIs, timers, and optional corpus/export tooling.
* **Worker machine** — runs the worker-gate API used by Codex jobs, tracks processes and telemetry, and stores project workspaces and evidence.

You can also run both roles on one host for development.

For the canonical current reference host paths, storage authority, worker gate,
paper gate, and compatibility boundaries, see
[current runtime snapshot](/current-runtime-snapshot).

## Prerequisites

Control VM:

* Linux with systemd
* Python 3.11+
* `uv`
* `git`
* network access to the worker API

Worker machine:

* Linux with systemd or an equivalent process manager
* Python 3.11+
* `uv`
* `git`
* the Codex stack used by your dispatch script
* NVIDIA telemetry libraries if you want GPU visibility

## Install the control plane

```bash theme={null}
git clone https://github.com/alias8818/enoch-agentic-research-system.git
cd enoch-agentic-research-system
uv venv --python /usr/bin/python3 .venv
uv pip install --python .venv/bin/python -e .
uv run pytest -q
```

The helper can copy the checkout into `/opt`, create config and state directories, install dependencies, and write systemd units when run as root:

```bash theme={null}
sudo scripts/install-control-plane.sh \
  --prefix /opt/enoch-control-plane \
  --config-dir /etc/enoch-control-plane \
  --state-dir /var/lib/enoch-control-plane \
  --user enoch
```

The script defaults to those paths when you omit the flags. Edit `/etc/enoch-control-plane/config.json` before enabling the service. Replace every placeholder token and URL.

## Configure required secrets

Generate distinct values for `control_api_bearer_token`, `completion_callback_token`, and `worker_wake_gate_bearer_token`:

```bash theme={null}
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
```

Never commit live config files, legacy Notion tokens, Pushover credentials, provider API keys, private hostnames, or production logs.

## Run the control service

```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now enoch-control-plane.service
sudo systemctl status enoch-control-plane.service
curl -fsS http://127.0.0.1:8787/healthz
```

Open the **canonical** operator console and authenticate with the configured inbound token:

```text theme={null}
http://<control-vm>:8787/control/dashboard-v2
```

`/control/dashboard` 307-redirects to `/control/dashboard-v2`. The shorter `/dashboard` legacy worker-gate shell also redirects to V2. The dashboard uses bounded `/control/api/v1/*` read models by default, so the first screen stays focused on operator questions rather than raw JSON.

## Configure the worker

On the worker host:

```bash theme={null}
git clone https://github.com/alias8818/enoch-agentic-research-system.git
cd enoch-agentic-research-system
scripts/install-worker.sh
```

The worker can run the same app with a worker-focused config:

```bash theme={null}
ENOCH_CONFIG=$HOME/.config/enoch-worker/config.json \
  uv run uvicorn enoch_control_plane.app:app --host 0.0.0.0 --port 8787
```

```bash theme={null}
curl -fsS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/control/health
```

## Optional timers

The repo includes systemd units for optional workflows:

* `enoch-queue-alert-check.timer` — periodically checks queue health and can send Pushover alerts when configured.
* `enoch-notion-sync.timer` — legacy compatibility only; syncs Notion intake/projection data when those environment variables are configured.
* `enoch-paper-draft-next.timer` — legacy unattended paper drafting path. Keep disabled unless you intentionally run a controlled, bounded drain and have verified the decision gate.

Enable only the timers you have configured and tested.

## Smoke-test before live dispatch

```bash theme={null}
export ENOCH_BASE_URL=http://<control-vm>:8787
export ENOCH_CONTROL_TOKEN=<control_api_bearer_token>
scripts/smoke-test-local.sh
```

The smoke script uses `/control/api/v1/overview` by default. That keeps the test bounded and aligned with the operator dashboard. If you need the broader compatibility payload, set `ENOCH_STATUS_ENDPOINT=/control/api/status` explicitly.

Then test worker preflight:

```bash theme={null}
curl -fsS -H "Authorization: Bearer $ENOCH_CONTROL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"wake_gate_url":"http://<worker>:8787","bearer_token":"<worker-token>","require_paused":false,"strict":false}' \
  "$ENOCH_BASE_URL/control/api/preflight" | python3 -m json.tool
```

Use dry-run dispatch first:

```bash theme={null}
curl -fsS -H "Authorization: Bearer $ENOCH_CONTROL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"dry_run":true,"requested_by":"operator-smoke-test"}' \
  "$ENOCH_BASE_URL/control/dispatch-next" | python3 -m json.tool
```

## Paper artifact workflow

Paper generation is optional and depends on evidence and paper rows. The default `paper_writer_provider` is `deterministic`. The code also supports an OpenAI-compatible `synthetic.new` provider with `paper_writer_base_url`, `paper_writer_model`, and `paper_writer_api_key` settings.

Do not publish generated artifacts until corpus import, packaging/provenance checks, and strict claim/evidence audit status are explicit. Human review or replication may still be needed before treating claims as reliable science, but paper finalization itself is automated.

The public paper counts should stay gate-aware:

* `paper_pipeline.write_needed` is actionable positive work only.
* `paper_pipeline.raw_completed_no_paper_candidates` is informational/debug only.
* `paper_pipeline.publish_ready` means finalized drafts still missing corpus import.

## Review the dispatch flow

Every live dispatch request passes through the following checks in order. Understanding this sequence helps you diagnose failures at each stage:

1. No conflicting active GPU lane exists.
2. A queue item exists.
3. Live dispatch is enabled (`live_dispatch_enabled` must be `true` in config).
4. The control plane is not paused and maintenance mode is not active.
5. Worker preflight is healthy.
6. The dispatch script launches the agent run.
7. The worker gate tracks process and telemetry truth.
8. The completion callback or status update is emitted only after the gate is satisfied.

<Tip>
  Always use dry-run dispatch first when testing a new deployment. It exercises the core dispatch guards without launching a real agent run. Worker preflight is only performed during live dispatch, not dry-run.
</Tip>

## What is not included

This repository does not include live secrets, private production config, generated paper corpus artifacts, old workflow-tool exports, private run state databases, or production logs. Those are intentionally excluded. Use the example config and this guide to recreate a clean deployment from scratch.
