2026-07-25 — Hosted tenant density ADR (0005)
Explored packaging demarkus as a hosted knowledge base service, then drafted repo ADR 0005 settling the density question the as-a-service plan left open.
Where it started
Question was whether we can compose a knowledge base service. The product reading (hosted, signup, multi-tenant) is already sketched in /plans/demarkus-as-a-service.md, so the session went straight to its unresolved fork: the plan names idle-tenant economics as the go/no-go gate for open signup and in the same section forbids the only lever that addresses it ("server remains one-world-per-process by design"). That is a deferred decision, and the control plane has to be built on one assumption or the other.
The finding that changed the argument
I expected in-process multi-tenancy to be blocked on the wire format, since protocol.Request is {Verb, Path, Metadata, Body} with no tenant selector. It is not. The client sets TLS ServerName from the hostname it dials (client/fetch/fetch.go around L383) and the server already takes a GetCertificate callback receiving ClientHelloInfo, so a connection can be bound to a world at handshake via SNI: per-tenant DNS name, wildcard cert, broker world entry pointing at one shared Service. Resolution is once per connection, not per request. Zero protocol change.
So the mechanism is cheap. That moved the whole argument off feasibility and onto isolation, which is where it belonged.
Why the ADR still says no
Today a world is enforced four separate ways: a process, a PVC, a token file, a certificate. Collapsing those into a map lookup in one address space means any path-scoping or catalog-keying bug becomes cross-tenant disclosure instead of a bug inside one tenant's world, and one panic becomes a multi-tenant outage. For a single-maintainer project that is the wrong risk for a cost curve that does not bite yet.
The second half of the argument matters as much: density only pays off with many dormant tenants, meaning a consumer tier, and the plan itself concludes a free tier is viable only after density works. Team tenants are not dormant. So B is speculative work on the one component that cannot be thrown away. Decision landed on option C (one world per process, priced to cover a warm pod, team-sized tenants), with scale-to-zero also declined for now.
Details worth keeping
- Rejected alternatives recorded in the ADR so the next pass does not re-derive them: a tenant key in request metadata or a
/tenants/<id>/path prefix would put the tenancy scheme into document identity, so every publishedmark://URL, graph edge, and OKF export would carry it. Per-tenant ports break the default 6309 dial that every client and join string assumes. - No world column in the postgres schema. Tenancy in the store contract drags it into the
storetestconformance suite, and by the backend-parity principle the file backend would have to implement it too, which it cannot do well. Schema or database per world keeps tenancy above the store. - pgstore is still the right backend for hosted worlds (external storage, backups, multi-replica reads). What was rejected is pgstore as the enabler for one process serving many tenants. Note the existing comment in
main.go: LOOKUP is served from the postgrescatalogtable specifically so a per-process index cannot diverge across pods, so the postgres path is already multi-pod safe for a single world. - Reversal trigger written into the ADR: revisit when dormant-tenant infra cost exceeds their revenue, or when a consumer tier is actually decided. That follow-up must clear an isolation bar this ADR does not attempt, namely a conformance suite proving cross-world access is impossible through every verb, plus a per-world resource ceiling, before any shared-process code ships.
Product framing raised, not settled
Personal-library hosting is a crowded, low willingness-to-pay market. The differentiators that exist here (agent-native read and write, OKF export as real portability, the promote pipeline) sell to teams already running Claude Code. Team tenants also happen to defuse the idle-economics problem, which is part of why option C is coherent. Separately: install-stack.sh is already a five-minute appliance, so self-host plus paid support is a commercial step with near-zero new engineering and none of the uptime, backup, or GDPR burden of hosting.
State
Branch docs/adr-0005-tenant-density, file docs/adr/0005-hosted-tenant-density.md, status proposed, uncommitted. Open follow-up offered but not done: revise the sequencing sketch in /plans/demarkus-as-a-service.md, whose step 4 is a scale-to-zero go/no-go gate this ADR removes from the near path.