Connect Sybil to the Pi root conversation with authenticated REST and WebSocket APIs, persistent read state, and a dedicated agent tab. Keep optimistic messages stable through delivery and retries, and animate arrivals without losing the reader's scroll position. Share bubble styling with regular chats and remove the obsolete admission sender. Reject malformed upstream receipts and WebSocket upgrade requests, with regression coverage and updated API documentation. Validated with 51 web tests, 44 server tests, both production builds, desktop/mobile bubble comparisons, and 11 browser animation and scrolling checks.
4.9 KiB
Sybil Server
Backend API for:
- LLM multiplexer (OpenAI Responses / Anthropic / xAI Chat Completions-compatible Grok / Gemini / Hermes Agent)
- Personal chat database (chats/messages + LLM call log)
Stack
- Node.js + TypeScript
- Fastify (HTTP)
- Prisma + SQLite (dev)
Quick start
cp .env.example .env
npm run dev
Migrations are applied automatically on server startup (prisma migrate deploy).
Open docs: http://localhost:8787/docs
API contract docs for clients:
../docs/api/rest.md../docs/api/streaming-chat.md
Run Modes
npm run dev: runssrc/index.tswithtsxin watch mode (auto-restart on file changes). Use for local development.npm run start: runs compileddist/index.jswith Node.js (no watch mode). Use for production-like runs.
Both modes run startup checks (predev / prestart) and apply migrations at app boot.
Auth
Set ADMIN_TOKEN and send:
Authorization: Bearer <ADMIN_TOKEN>
If ADMIN_TOKEN is not set, the server runs in open mode (dev).
Env
OPENAI_API_KEYANTHROPIC_API_KEYXAI_API_KEYGEMINI_API_KEYHERMES_AGENT_API_BASE_URL(http://127.0.0.1:8642/v1by default; include the/v1suffix)HERMES_AGENT_API_KEY(enables the Hermes Agent provider; set to HermesAPI_SERVER_KEY, or any non-empty value if that local server does not require auth)HERMES_AGENT_MODEL(optional fallback/override model id; defaults client-side tohermes-agent)EXA_API_KEYBRAVE_SEARCH_API_KEY(required whenCHAT_WEB_SEARCH_ENGINE=brave)CHAT_WEB_SEARCH_ENGINE(exaby default;braveandsearxngare also supported for chat tool calls only)SEARXNG_BASE_URL(required whenCHAT_WEB_SEARCH_ENGINE=searxng; instance must allowformat=json)CHAT_MAX_TOOL_ROUNDS(100by default; maximum model/tool result cycles per chat completion)CHAT_CODEX_TOOL_ENABLED(falseby default; enables thecodex_execchat tool for managed-tool providers)CHAT_CODEX_REMOTE_HOST(required when Codex tool is enabled; SSH host/IP oruser@host)CHAT_CODEX_REMOTE_USER(optional SSH user when host does not include one)CHAT_CODEX_REMOTE_PORT(22by default)CHAT_CODEX_REMOTE_WORKDIR(/workspace/sybil-codexby default; created and reused on the devbox)CHAT_CODEX_SSH_KEY_PATH(recommended: path to a read-only mounted private key)CHAT_CODEX_SSH_PRIVATE_KEY_B64(optional fallback private key delivery)CHAT_CODEX_EXEC_TIMEOUT_MS(600000by default)CHAT_SHELL_TOOL_ENABLED(falseby default; enables theshell_execchat tool for managed-tool providers on the same devbox)CHAT_SHELL_EXEC_TIMEOUT_MS(120000by default)
API
GET /healthGET /v1/auth/sessionGET /v1/chatsPOST /v1/chatsGET /v1/chats/:chatIdPOST /v1/chats/:chatId/messagesPOST /v1/chat-completionsPOST /v1/chat-completions/stream(SSE)GET /v1/searchesPOST /v1/searchesGET /v1/searches/:searchIdPOST /v1/searches/:searchId/runPOST /v1/searches/:searchId/run/stream(SSE)
Search runs now execute both Exa searchAndContents and Exa answer, storing:
- ranked search results (for result cards), and
- a top-level answer block + citations.
When chatId is provided to completion endpoints, you can send full conversation context. The server now stores only new non-assistant messages to avoid duplicate history rows.
POST /v1/chat-completions body example:
{
"chatId": "<optional chat id>",
"provider": "openai",
"model": "gpt-4.1-mini",
"messages": [
{"role":"system","content":"You are helpful."},
{"role":"user","content":"Say hi"}
],
"temperature": 0.2,
"maxTokens": 256
}
Next steps (planned)
- Better streaming protocol compatibility (OpenAI-style chunks + cancellation)
- Tool/function calling normalization
- User accounts + per-device API keys
- Postgres support + migrations for prod
- Attachments + embeddings + semantic search
Persistent Pi Agent
Configure the backend with:
PI_AGENT_BASE_URL=http://pi-durable:8788
PI_AGENT_API_KEY=<the Pi service's PI_API_KEY>
The base URL defaults to http://pi-durable:8788 and has no /v1 suffix. In Docker,
attach Sybil's server container and Pi to the shared agents network so that
pi-durable resolves privately. Supply the key through a private server environment
file; it is never a VITE_* variable or browser setting. Keep the existing Sybil
ADMIN_TOKEN authentication for clients.
The Agent tab uses /v1/agent, /v1/agent/messages, and /v1/agent/ws to proxy the
same persistent Pi root conversation across devices. Pi remains the source of
truth for messages and background replies. Sybil stores only the shared,
monotonically increasing read cursor in SQLite through /v1/agent/read-state;
its migration is applied automatically at startup. The bundled nginx configuration
forwards WebSocket upgrades. See docs/api/rest.md and
docs/api/streaming-chat.md at the repository root for the client contracts.