self-host
Run Wardstone
One container. The models ship inside the image.
1 · Config
The only value to change is sink.url, where verdicts are POSTed.
{
"http": { "port": 8090 },
"artifacts_dir": "/artifacts/2026.10.1",
"metrics_path": "/data/wardstone.metrics",
"rings": { "data_dir": "/data" },
"sink": { "url": "https://your-moderation-service/wardstone/verdicts",
"hmac_secret_env": "WARDSTONE_SINK_SECRET" }
}
2 · Run
Keep the /data volume: it holds queued work across restarts.
docker volume create wardstone-data
docker run -d --name wardstone --restart unless-stopped \
-p 8090:8090 \
-v wardstone-data:/data \
-v $PWD/wardstone.json:/app/config/wardstone.json:ro \
-e WARDSTONE_SINK_SECRET=<shared secret> \
wardstone:<version>
curl localhost:8090/healthz # ok, ready after 10–60 s
3 · Send messages
Same conversation_id per chat, same author per user, unique message_id, in order. Or stream binary frames over ws://host:8090/v1/stream.
curl -s localhost:8090/v1/messages -H 'Content-Type: application/json' -d '{
"messages": [
{"conversation_id": "c42", "message_id": "1001", "author": "u7", "text": "...", "ts": 1791712345123}
]}'
4 · Receive verdicts
Verify the signature, return 2xx, ignore verdict ids you have already seen. Format: API.
5 · Check
curl localhost:8090/v1/status # queued work, drops, model version
docker logs wardstone # never contains message text