Skip to content

Deployment & config

How to run the CTXone Hub, and the configuration choices that matter — the shared daemon (the standard setup), running it as a service, giving an agent its own memory, and securing the hub with a token when you expose it beyond localhost. If you followed the Quickstart and everything works, you already have the standard setup; this page is the reference for the rest.

One hub owns one db file at a time. On startup a hub writes a PID lockfile next to the db (<db>.lock). If a second hub tries to open the same --path while the first is alive, it refuses to start:

database is already locked by ctxone-hub pid 12345 (lockfile: …/memory.db.lock);
refusing to start a second hub against the same db

This is deliberate — two hubs writing one SQLite file corrupts it (the 2026-04-28 incident). See DATA_SAFETY.md. Everything below is really about who holds that single lock.

ctxone-hub has two transports for talking to agents, plus the HTTP surfaces:

ModeCommandServesOwns the db
MCP stdioctxone-hub --path DBMCP tools to the spawning agentyes, while the agent runs
HTTP (+Lens)ctxone-hub --http --lens --path DBREST API + web UI + MCP at /mcpyes, while the daemon runs

The standard setup is one shared HTTP daemon. An --http hub serves MCP (at /mcp, Streamable HTTP) + REST + the Lens web UI from one process, and ctx init (default --transport http) points every tool at it by URL — no lockfile race, web UI always up, all tools share one graph. ctx init --transport stdio is the escape hatch: each tool spawns its own hub that holds the lock, so you can’t also run a web UI on that db. Design notes: the unified-hub design.

Most setups want Topology B (the shared daemon) — it’s what ctx init writes by default. Topology A is the no-daemon fallback; C and D cover isolation and one-off inspection.

ctx init --transport stdio — each tool spawns and owns its own stdio hub. Nothing else touches the db.

  • ✅ Zero extra processes, no port, no daemon to run.
  • ❌ No Lens; can’t attach the CLI-over-HTTP to this db while the agent runs (the CLI reads the same graph, but only through a running HTTP hub). One tool per db — a second tool needs its own db (topology C).

B. Shared HTTP daemon — the default & standard

Section titled “B. Shared HTTP daemon — the default & standard”

Run one long-lived hub that serves everything, and point agents, the CLI, and Lens at it. Nothing spawns its own child; there is exactly one db owner. This is what a plain ctx init configures.

Terminal window
# 1. Run the one daemon (ideally as a login service — see below — so it's up at boot).
ctxone-hub --http --lens --path ~/.ctxone/memory.db
# 2. Point the CLI at it (defaults to http://localhost:3001, so usually optional).
export CTX_SERVER=http://localhost:3001
# 3. Configure your tools to connect by URL. http is the default, so plain:
ctx init # all detected tools
ctx init --tool claude # or one tool

ctx init (default --transport http) writes an MCP entry like:

{
"mcpServers": {
"ctxone": { "type": "http", "url": "http://localhost:3001/mcp?namespace=<detected>" }
}
}

The ?namespace= is filled in from the project detected in the current directory (same scoping a per-project stdio hub would use). Override the endpoint with --mcp-url, e.g. --mcp-url http://mybox:3001/mcp.

  • ✅ One process, one lock, startup order irrelevant — the reboot race is gone. Lens always up, independent of any agent; multiple agents share one graph.
  • MCP clients reach the daemon two ways, and ctx init --transport http writes the right one per client:
    • Native URL — Claude Code, Cursor, VS Code get {"type":"http","url":…}.
    • mcp-remote bridgeClaude Desktop has no native HTTP MCP support, so ctx init writes an stdio bridge (npx -y mcp-remote <url> --transport http-only) that proxies to the daemon. Requires Node/npx on PATH; a note is printed when this is used.
    • Codex — native HTTP via a url key in config.toml ([mcp_servers.ctxone]\nurl = "…/mcp?namespace=…").
  • ⚠️ A single daemon has no per-cwd project detection, so one agent config maps to one namespace (baked into the URL). An agent that roams many repos uses one fixed namespace or per-workspace configs. Need per-cwd auto-scoping? Use stdio (topology A) — it’s still fully supported.

C. Give an agent its own dedicated db (isolation)

Section titled “C. Give an agent its own dedicated db (isolation)”

Point one agent at a different --path so it never contends with your shared hub or another agent. Two separate files → two separate locks → no conflict, and two independent memory graphs.

Edit that agent’s MCP config (or run ctx init --tool <name> after setting the path). The stdio entry looks like:

// e.g. ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"ctxone": {
"command": "/opt/homebrew/bin/ctxone-hub",
"args": ["--path", "/Users/you/.ctxone/claude.db", "--agent-id", "claude-desktop"]
}
}
}

Use this when:

  • You want Claude’s memory kept separate from a shared/team graph.
  • Two agents on one machine should not see each other’s memory.
  • You’re experimenting and don’t want to touch your main graph.

Isolated dbs do not sync. A fact remembered in claude.db is invisible to the hub serving memory.db. To browse an isolated db in Lens, run a hub on it at a different port while the agent is stopped, or on a copy: ctxone-hub --http --lens --path /Users/you/.ctxone/claude.db --port 3002.

D. Browse a live agent’s memory without fighting the lock

Section titled “D. Browse a live agent’s memory without fighting the lock”

You can’t open a second hub on a locked db. To look at the graph an agent is actively using, snapshot it and serve the copy:

Terminal window
cp ~/.ctxone/memory.db /tmp/lens.db
ctxone-hub --http --lens --path /tmp/lens.db --port 3002

Read-only snapshot; it won’t reflect writes after the copy. (The proper fix is the shared daemon in topology B — one owner, Lens live on the same graph the agents write.)

ctx init points agents at the canonical path:

  • macOS / Linux: ~/.ctxone/memory.db
  • Windows: %APPDATA%\ctxone\memory.db

Override per agent with --path (topology C). A bare ctxone-hub with no --path defaults to ./target/ctxone.db (dev convenience) — always pass --path in real setups.

FlagPurpose
--path <PATH>SQLite db file (per-agent isolation lives here)
--httpEnable the REST API
--lensServe the web UI at / (requires --http)
--port <N>HTTP port (default 3001) — use distinct ports for parallel dbs
--agent-id <NAME>Attribution recorded on commits (ctx blame)
--auth-token <TOK>Bearer token guarding REST + /mcp (env CTXONE_AUTH_TOKEN); loopback exempt
--allowed-origin <ORIGIN>Extra browser origin allowed to call the API (repeatable; same-origin is always allowed)
--initCreate the db file if missing (guards against path typos)
--storage memoryEphemeral, no file, no lock — good for tests/demos
--asd-path name=PATHRegister an ASD code-graph repo for the process pool
VarEffect
CTX_SERVERHub URL the ctx CLI targets (default http://localhost:3001)
CTX_NAMESPACEProject namespace for CLI/MCP scoping
CTX_BRANCHDefault memory branch/ref
CTX_TOKENBearer token the ctx CLI sends (for a remote authed hub)
CTXONE_AUTH_TOKENSame as --auth-token (bearer guarding REST + /mcp)
CTXONE_ALLOWED_ORIGINSComma/space-separated extra allowed browser origins
CTXONE_BACKUP_KEEPStartup snapshots to retain (default 5)
CTXONE_BACKUP_INTERVAL_SECSBackground VACUUM-INTO snapshot interval (default 1800; 0 disables)
RUST_LOGLog level (info default)

The hub binds 0.0.0.0 (all interfaces). With no token set, REST and /mcp are reachable from the network unauthenticated — the hub logs a loud warning at startup. Before exposing it beyond localhost, set a bearer token:

Terminal window
ctxone-hub --http --lens --path ~/.ctxone/memory.db --auth-token "$(openssl rand -hex 32)"
# or: CTXONE_AUTH_TOKEN=… ctxone-hub --http …

Behaviour:

  • Loopback peers (127.0.0.1/::1) are always exempt — local CLI, Lens, and same-host agents keep working with no token.
  • Non-loopback requests must send Authorization: Bearer <token> (else 401).
  • Setting a token also relaxes /mcp’s loopback-only Host check so authenticated remote clients can connect (the bearer becomes the gate).

How each client gets the token:

  • ctx CLI — pass --token <TOK> or set CTX_TOKEN (only for a remote hub).
  • AI toolsctx init --transport http writes it into each tool’s config:
  • ctx init --transport http --auth-token <TOK> — embeds a literal bearer: a headers entry for native http (Claude Code/Cursor/VS Code) and a --header arg for the mcp-remote bridge (Claude Desktop). The token is written in plaintext into those config files (a warning is printed).
  • ctx init --transport http --auth-token-env <VAR> — keeps the secret out of config files: Codex gets bearer_token_env_var = "<VAR>", and native http entries reference Bearer ${VAR} (for clients that expand env vars). The mcp-remote bridge can’t expand env vars, so Claude Desktop needs the literal --auth-token form.

Independent of the token, the hub rejects browser requests whose Origin isn’t same-origin or allow-listed — so a page you visit can’t drive the loopback hub (loopback peers are auth-exempt, which is why this guard matters). CLI and native MCP clients send no Origin and are unaffected; Lens is same-origin and always allowed. CORS reflects only same-origin/allow-listed origins (never *).

To let a real cross-origin browser app (e.g. Lens hosted elsewhere) use the API:

Terminal window
ctxone-hub --http --allowed-origin https://lens.example.com # repeatable
# or CTXONE_ALLOWED_ORIGINS="https://a.example, https://b.example"

Lens over an authenticated hub — use a tunnel

Section titled “Lens over an authenticated hub — use a tunnel”

Lens is a browser SPA; it can’t attach an Authorization: Bearer header to its requests. So when a token is set, a remote browser hitting Lens directly is rejected by the auth layer (loopback is exempt, remote is not). Treat Lens as a local admin UI and reach it remotely over an SSH tunnel, which makes your browser a loopback client of the hub:

Terminal window
ssh -L 3001:localhost:3001 you@hub-host # then open http://localhost:3001

The hub prints this reminder at startup when --lens and --auth-token are both set. (REST and /mcp remain directly reachable with the bearer token; only the browser UI needs the tunnel.)

To make the HTTP/Lens hub survive reboots and start before any agent (so it, not an agent’s stdio child, owns the db), run it as a login service. ctx service generates and registers the unit for you — launchd on macOS, a systemd user unit on Linux, and a Task Scheduler logon task on Windows (via schtasks):

Terminal window
ctx service install # lens on, port 3001, canonical db
ctx service install --port 3001 --path ~/.ctxone/memory.db
ctx service install --auth-token "$(openssl rand -hex 32)" # embeds token (chmod 600)
ctx service install --dry-run # print the unit + commands, write nothing
ctx service status # is it registered/running?
ctx service uninstall # stop + remove

The unit runs ctxone-hub --http --lens --path <db> --port <port> with RunAtLoad/KeepAlive (macOS), Restart=on-failure (systemd), or a LogonTrigger + RestartOnFailure (Windows), logging to ~/.ctxone/hub.log. Preview it first with --dry-run. If another hub already holds the db (an agent’s stdio hub, or a manual one), the service will fail on the lockfile — stop it first. On Windows the auth token can’t be embedded in the task; set it with setx CTXONE_AUTH_TOKEN <token> instead.

Equivalent hand-written launchd plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.ctxone.hub</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/ctxone-hub</string>
<string>--http</string>
<string>--lens</string>
<string>--path</string>
<string>/Users/you/.ctxone/memory.db</string>
</array>
<key>KeepAlive</key><true/>
<key>RunAtLoad</key><true/>
<key>StandardErrorPath</key><string>/Users/you/.ctxone/hub.log</string>
</dict>
</plist>
Terminal window
launchctl load -w ~/Library/LaunchAgents/com.ctxone.hub.plist

With the daemon running, point agents at it with ctx init --transport http and they stop spawning their own hub — one process, one owner, no race. ctx init handles every detected client (native URL, Codex url, or the mcp-remote bridge for Claude Desktop), so they all connect to the daemon. Use a separate db (topology C) only for deliberate isolation, or for a client that supports neither native http nor the mcp-remote bridge. See the unified-hub design.

  • database is already locked … — another hub owns that db. Find it (ps aux | grep ctxone-hub), or use a different --path/--port. Stale locks (owner gone) are reclaimed automatically.
  • Lens shows nothing / CLI can’t connect — check CTX_SERVER and that a hub is running with --http. curl http://localhost:3001/api/health.
  • Agent and Lens show different memory — they’re on different db paths (topology C). That’s isolation working as configured.

See also: ARCHITECTURE.md · DATA_SAFETY.md · TROUBLESHOOTING.md