What self-hosting usually costs
Self-hosting a memory platform normally means running the memory service plus everything it depends on: a relational database, a vector store such as Qdrant, Milvus, Weaviate or pgvector, and often a local model server. Each of those is a process to patch, back up, monitor and be paged about.
Titen was built to the opposite constraint. Its product principles require that no graph database, queue, Redis, Postgres, Docker or LLM is mandatory for the first useful path, and that canonical storage works without a hosted dependency, with indexes rebuildable from it.
The base service holds that line. It does not require Docker, Redis, Postgres, a graph database, or a vector database, and every candidate is hydrated from canonical SQL before it is returned.
Deploy on a VPS →Install it, then read it
The package declares no dependencies block at all. Its single optional peer dependency, sqlite-vec, matters only if you turn on semantic retrieval, and it is loaded through a lazy require: an absent install degrades to lexical FTS5 and /readyz reports the capability as disabled rather than failing the service.
curl -fsSL https://titen.dev/install.sh | bash
titen bootstrap --org 'My Org'
titen serve # 127.0.0.1:8787, one SQLite file
curl --fail http://127.0.0.1:8787/readyz # runtime, schema, checks, capabilities- src/core/** has zero external imports and uses Web Standards only, which is what lets one kernel run on bun:sqlite and on Cloudflare D1 against a single contract suite.
- bun:sqlite appears in exactly one file, src/runtime/bun/sqlite.ts.
- Backup is a file copy plus a versioned JSONL export that imports back.
Verify the egress claim yourself
Buyers in this category are coached not to trust vendor egress claims, which is fair. Checking this one takes about a minute, because there is very little to read.
rg -n 'fetch\(' src/runtime src/core
# src/runtime/bun/vectors.ts:206 your embedding endpoint, only when TITEN_EMBED_* is set
# src/runtime/bun/server.ts:143 Bun.serve's inbound handler, not an outbound call
# src/runtime/cloudflare/worker.ts:104 the Worker's inbound handler, not an outbound call- Two more outbound paths go through an injected transport rather than a literal fetch, so search them by name: RELEASE_URL in src/runtime/bun/release.ts reads titen.dev/version.json only when you run titen version --check, and dispatchWebhook in src/core/webhook-security.ts delivers to gateway URLs you register yourself.
- Webhook delivery refuses to run at all unless you have configured its security and signing key, and it pins the connected address against the destination it resolved.
- There is no telemetry or analytics code in the repository. The license is Apache-2.0, so nothing here rests on taking that on trust.
Where this stops
Air-gapped is a word Titen has not earned. Installing it needs a network — the installer fetches Bun, and npm or bun install fetches the package. There is no air-gap attestation and no third-party security audit.
The default lexical lane costs retrieval quality, and the repository publishes the number instead of hiding it. On one neutral fixture, the same build scored recall@1 0.4400 FTS-only against 0.8000 with the vector lane enabled. Quality also falls as the corpus grows: FTS-only recall@1 went from 1.00 at 1,000 claims to 0.49 at 100,000.
- Semantic retrieval is opt-in and all-or-nothing. Set one TITEN_EMBED_* variable and every one of them must then be valid, or /readyz fails closed with 503 and no vector query runs.
- The packaged vector path is verified on glibc Linux x64 with Bun 1.3.13. Other platforms need their own ready, drain and query smoke.
- Rootless Quadlet/Podman and systemd-user restart are verified on one host, with both listeners loopback-only. Tailscale Serve and Cloudflare Tunnel ingress are documented and copyable, but neither is claimed active.