Standalone server
@graphorin/server is the optional standalone runtime. Same library packages, different lifetime — promote your assistant to a daemon with a network API the moment it has to outlive a single Node.js process.
Library mode is the default
You only need the standalone server when:
- your assistant has to survive process restart, OR
- you expose it over the network (browser, Slack bot, mobile app), OR
- you want durable triggers (cron, interval, idle, event).
For a CLI script or a desktop app, embed the library packages directly. See Architecture § Two ways to ship.
Capabilities
| Capability | Library mode | Standalone server |
|---|---|---|
| Agent runs | ✓ | ✓ |
| Memory + sessions | ✓ | ✓ |
| Workflows | ✓ | ✓ |
| Tools / Skills / MCP | ✓ | ✓ |
| Durable HITL across process restart | ✓ (with the right checkpoint store) | ✓ |
| Triggers (cron / interval / idle / event) | manual scheduling | ✓ daemon |
| REST + WebSocket + SSE | — | ✓ |
| Server-token authentication | — | ✓ |
| Prometheus metrics endpoint | — | ✓ |
| Health checks | — | ✓ |
| Replay endpoint | manual via SQLite | ✓ |
REST surface
Built on hono (MIT) and @hono/node-server (MIT). The default basePath is /v1. Every authenticated endpoint requires a bearer token signed with HMAC-SHA256 against the deployment-wide pepper. The unauthenticated /v1/health route is exempt.
| Method | Path | Purpose |
|---|---|---|
GET | /v1/health | Liveness + readiness summary (probes for storage, audit log, secrets, triggers, encryption). |
GET | /v1/health/secrets | Authenticated drilldown of the active SecretsStore. |
GET | /v1/metrics | Prometheus exposition (path configurable; auth optional). |
GET | /v1/agents | List registered agents. |
GET | /v1/agents/:id | Describe a single agent. |
POST | /v1/agents/:id/run | Run an agent synchronously and return the final output. |
POST | /v1/agents/:id/stream | Start a streaming run; returns runId plus subscription metadata for the SSE / WebSocket channel. |
GET | /v1/runs/:runId/state | Read the current RunState. |
POST | /v1/runs/:runId/abort | Abort a run. |
POST | /v1/runs/:runId/resume | Resume a paused run with a directive (Phase 14a stub today). |
POST | /v1/runs/:runId/replay | Replay a recorded run from the audit / cassette artefacts. |
GET | /v1/sessions | List sessions. |
POST | /v1/sessions | Create a session. |
GET | /v1/sessions/:id | Read a session. |
DELETE | /v1/sessions/:id | Delete a session. |
GET | /v1/sessions/:id/messages | List messages. |
GET | /v1/sessions/:id/handoffs | List handoffs. |
POST | /v1/sessions/:id/export | Stream a JSONL export. |
POST | /v1/sessions/:id/replay | Replay a recorded session. |
POST | /v1/memory/search | Hybrid search across the memory tiers. |
POST | /v1/memory/facts | Persist a fact. |
DELETE | /v1/memory/facts/:id | Soft-delete a fact. |
POST | /v1/memory/blocks | Define / update a working block. |
DELETE | /v1/memory/blocks/:label | Detach a working block. |
GET | /v1/skills | List loaded skills. |
GET | /v1/skills/:name | Describe a skill. |
POST | /v1/skills/install | Install a skill from a configured source. |
GET | /v1/mcp/servers | List configured MCP servers. |
POST | /v1/mcp/servers | Register a new MCP server connection. |
DELETE | /v1/mcp/servers/:id | Disconnect a server. |
GET | /v1/audit | Tailable audit log. |
POST | /v1/audit/verify | Walk + verify the SHA-256 hash chain. |
POST | /v1/audit/export | Export the audit log. |
GET | /v1/tokens | List server tokens. |
POST | /v1/tokens | Issue a token. |
DELETE | /v1/tokens/:id | Revoke a token. |
GET | /v1/triggers | List configured triggers. |
GET | /v1/workflows | List configured workflows. |
POST | /v1/auth/session/ws-ticket | Mint a single-use WebSocket session ticket. |
WebSocket protocol
@graphorin/protocol ships the graphorin.protocol.v1 contract — a typed message envelope for live event streaming over WebSocket. Built on @hono/node-ws (MIT).
import { GraphorinClient } from '@graphorin/client';
const client = new GraphorinClient({
baseURL: 'wss://assistant.example.com',
token: process.env.SERVER_TOKEN,
});
for await (const event of client.runAgent('planner', { prompt: 'Plan a hike.' })) {
if (event.type === 'text.delta') process.stdout.write(event.delta);
}The browser-friendly client is published as @graphorin/client and depends only on @graphorin/protocol. SSE is the documented fallback for environments that cannot upgrade to WebSocket.
Triggers
@graphorin/triggers is the durable scheduling layer. Four trigger kinds:
| Kind | Spec |
|---|---|
cron | Standard 5-field cron expression. |
interval | Fixed interval in milliseconds. |
idle | Fires after N ms of agent inactivity. |
event | Listens on a named event channel. |
Declare a trigger using the four typed factories:
import { cron, interval, idle, event, createScheduler } from '@graphorin/triggers';
const morningSummary = cron(
'morning-summary',
'0 8 * * *',
async () => {
await agent.run('Send the morning summary.');
},
);
const scheduler = createScheduler({ store: sqlite.triggers });
await scheduler.register(morningSummary);
await scheduler.start();The triggers daemon (mounted by @graphorin/server) owns the schedule, persists the next-firing time across restarts, fires the registered callback, and audits every fire decision.
Idempotency
All POST endpoints accept an Idempotency-Key header. Repeated submissions within the configured TTL return the original response (or the in-flight result for a still-running call).
Disconnect policy
Long-running streams (agent runs, workflows) survive client disconnects through the configurable disconnect.policy:
| Policy | Behaviour on client disconnect |
|---|---|
'continue' (default) | Run continues; client reconnects via GET /v1/agents/:id/runs/:runId/follow. |
'pause' | Run is paused; resumed when the client reconnects with the same runId. |
'abort' | Run aborts with client-disconnected. |
Health checks
GET /v1/health returns:
{
"status": "ok",
"version": "0.1.0",
"checks": {
"storage": "ok",
"audit-log": "ok",
"secrets": "ok",
"triggers": "ok",
"providers": { "openai": "ok", "ollama": "unreachable" }
}
}Provider checks are passive — the server never opens an outbound connection it doesn't already have.
Prometheus metrics
GET /metrics exposes the counters from Observability in Prometheus exposition format, plus the standard process / Node.js metrics.
Configuration
# graphorin.config.toml
[server]
host = "127.0.0.1"
port = 8787
[storage]
path = "./assistant.db"
encryption-at-rest = "keyring:graphorin_db_key?service=graphorin"
[secrets]
backend = "keychain"
[triggers]
enabled = true
[observability]
exporter = "otlp"
otlp-url = "https://otel.example.com/v1/traces"The CLI command graphorin start --config graphorin.config.toml boots the server.
Process model
Recommended deployment patterns:
- systemd: ship the unit template the project provides under
examples/systemd/. - Docker: ship the image template under
examples/docker/. - Kubernetes: ship the manifests under
examples/k8s/.
All three templates run Graphorin as a non-root user with the audit log on its own mountpoint and the secrets store unreadable by the application's main filesystem path.
Next steps
- CLI —
graphorin start,graphorin doctor,graphorin token. - Deployment — production checklists.
- Security — server-token authentication, audit log.
- Observability — what gets traced.
Graphorin · v0.1.0 · MIT License · © 2026 Oleksiy Stepurenko