25 KiB
@atlilith — Designing the V3 Platform
Status: Design phase
Date: 2026-05-16
Lineage: v0 egirl-platform (viky-era, ambitious 27-app monorepo) → v1 lilith-platform (54-feature SaaS, shipped only partial) → v2 lilith-platform.live (Quinn-personal, currently in production) → v3 @atlilith (org-aware, provider-generic, multi-tenant)
1. Why a V3?
V2 works, but is outgrowing the single-person frame:
- Quinn now operates 2 parent orgs (Lilith Apps, Cocotte), a tour (ATT), and multiple brands (Cocotte, Maison Sansonnet, ftw.pw, future merche biche)
transquinnftwis both a standalone provider AND org-admin of Cocotte — V2 has no concept of "org"- V2's
user-datafeature should really beorg-analytics(analytics aggregating at both user AND org level) - V2's services are named
quinn.*— fine for Quinn's instance, but ossifies the "platform = Quinn" assumption that prevents onboarding new providers - V1 has features V2 dropped (marketplace, profile/attributes, bookings, payments, reviews, trust, streaming) that will be needed eventually — V3 architecture must accommodate mining them
V3 keeps everything V2 has working, generalizes the naming, and adds the tenancy model V2 lacks.
2. Core Concepts
Three concentric layers
┌──────────────────────────────────────────────────────────┐
│ PLATFORM (Lilith Apps ehf — the tech company) │
│ Infrastructure, shared services, admin │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PROVIDER (user / org tenants) │ │
│ │ - Person: transquinnftw, future-provider-x │ │
│ │ - Org: cocotte, sansonnet, future-merche-biche │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ CLIENT (the provider's customers) │ │ │
│ │ │ Bookings, messaging-from-client-side │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
Tenancy model: Person-first, Org-as-overlay
Person (primary tenant)
├── standalone mode (most providers start here)
└── may admin or join one or more Orgs (optional)
Org (optional overlay)
├── owner: 1 Person
├── admins: N Persons
├── members: N Persons
└── brands / domains / sub-entities attached
Worked example — transquinnftw + Cocotte:
transquinnftwis a Person tenant with their own profile (transquinnftw.com), inbox, bookings, analyticscocotteis an Org tenant with its own dashboard, members, brand sites (cocotte.maison)transquinnftwis the owner ofcocotte— when logged in, they see a context switcher:[ Personal | Cocotte ]- Future providers can:
- Operate as Person-only (no org needed)
- Be a Person + admin of an Org
- Be a Person who is just a member of someone else's Org (e.g., joining Cocotte as a managed talent)
What this is NOT
- ❌ Not "org-as-workspace" where Person logs into a single tenant context (Slack/Notion model)
- ❌ Not "single Person per Org" — Orgs can have multiple members
- ❌ Not mandatory — most providers will operate without ever touching an Org
3. Architecture Principles
- Person-first, Org-optional. Onboarding never asks "what's your org" — that's a later upgrade.
- Provider-generic in code. Internal package names contain no person/org name. Quinn's instance lives at
quinn.*domains, but the code isprovider-portal,ai-assistant,messenger, etc. - Sibling services stay sibling. mail-sync, mac-sync, knowledge-platform, agents — peer services consumed over HTTP/MCP. Not inside the platform monorepo.
- ML/AI work belongs in peer apps, never in the platform. Crystal-AI, image diffusion, translation, content moderation, classification, RAG — all live under
~/Code/@applications/@{ai,ml,imajin}/and are called over HTTP/MCP. The platform contains orchestration (request routing, prompt assembly, draft tracking), never weights or training data. If a v1 feature mixed orchestration + ML, port only the orchestration part. Noml-service/directories in@platform/. - Brand sites are templates. Cocotte, Sansonnet, ATT, future merche biche — each is an instantiation of
org-site/with config, not a forked codebase. - Monolith repo holds the full lineage. All three prior versions live inside this repo as zstd-compressed tarballs under
.archive/, tracked through Git LFS. The whole point of@atlilithis to be the canonical workspace — having v0/v1/v2 co-located here is by design, not a bloat problem. (Practical caveat: the LFS push hit Forgejo's reverse-proxyclient_max_body_sizelimit on the first attempt — that's a server config fix tracked in Phase 5.7, not an architectural pivot.) - No leakage between tenants. Org A's data invisible to Org B. Person A's data invisible to Org A unless explicitly shared.
- Schema is the contract. Add
orgs,org_members,org_idforeign keys. Migrations are forward-only; no downgrades after Phase 2.
4. Directory Layout
~/Code/@projects/@atlilith/
│
├── @platform/ ← V3 monorepo
│ ├── codebase/
│ │ ├── @apps/ ← user-facing apps (audience layer)
│ │ │ ├── platform-admin/ (was quinn.admin)
│ │ │ ├── provider-portal/ (was quinn.my — provider's dashboard)
│ │ │ ├── provider-site/ (was provider-website — public profile)
│ │ │ ├── org-site/ (template — cocotte-web, sansonnet-web)
│ │ │ ├── tour-site/ (template — adult-therapy-tours)
│ │ │ ├── ai-assistant/ (was quinn-ai)
│ │ │ ├── messenger/ (was quinn-messenger)
│ │ │ ├── landing/ (atlilith.com)
│ │ │ └── waitlist/
│ │ ├── @features/ ← cross-cutting domain features
│ │ │ ├── api/ (Hono gateway — port 3030)
│ │ │ ├── sso/ (org-aware JWT)
│ │ │ ├── org-analytics/ (was user-data)
│ │ │ ├── messages/
│ │ │ ├── mail-autoresponder/
│ │ │ ├── ai-engine/
│ │ │ ├── newsletter/
│ │ │ ├── client-intel/
│ │ │ ├── age-verification/
│ │ │ ├── image-protection/
│ │ │ ├── vip/
│ │ │ ├── merchant/
│ │ │ ├── tour-scout/ (was hotel-scout)
│ │ │ ├── price-watcher/
│ │ │ ├── edge-purge/
│ │ │ ├── db-monitor/
│ │ │ ├── seo/ (extracted, with v1's ML pSEO mined)
│ │ │ └── platform-seed/
│ │ └── @packages/ ← shared libs
│ │ ├── auth-provider/
│ │ ├── i18n/
│ │ ├── mailer/ (HTTP client for mail-sync peer)
│ │ ├── provider-api-client/
│ │ ├── app-switcher/ (was quinn-app-switcher)
│ │ ├── ui-auth/
│ │ └── ui-dev-content/
│ ├── deployments/
│ │ └── @domains/ ← per-domain deploy configs
│ │ ├── atlilith.com/
│ │ ├── quinn.*/ (Quinn's instance kept under quinn.*)
│ │ ├── cocotte.maison/
│ │ ├── sansonnet.maison/
│ │ ├── adulttherapytour.com/
│ │ └── ...
│ ├── infrastructure/ (postgres, timescale, redis, minio, mailpit)
│ ├── docs/
│ ├── scripts/
│ ├── CLAUDE.md
│ ├── package.json
│ ├── bunfig.toml
│ ├── bun.lock
│ └── tsconfig.json
│
├── .archive/ ← read-only history
│ ├── platform.0/ (egirl-platform, v0)
│ ├── platform.1/ (lilith-platform, v1)
│ ├── platform.2/ (lilith-platform.live, v2)
│ └── ARCHIVED.md (lineage notes + mining map)
│
├── tooling/ ← shared scripts (carried from @lilith)
├── .content-lifecycle/ ← content workflow config
├── CLAUDE.md ← project-root rules
├── DESIGN.md ← this file
└── README.md
Peer services (NOT inside @atlilith/)
These stay at their current locations and are consumed over HTTP/MCP:
~/Code/@projects/@lilith/mail-sync/— Proton Bridge wrapper (port 4444)~/Code/@applications/@mac-sync/— macOS iMessage bidirectional sync~/Code/@applications/@messenger/— separate messenger codebase (needs reconciliation with v2'smessagesfeature)~/Code/@applications/@agents/— Claude SDK agent monorepo (assistant, companion, egirl, nag, nudge, social, travel, quinn-voice, quinn-prospector)~/Code/@applications/@ml/knowledge-platform/— Crystal's successor (knowledge verification, content auditing)~/Code/@applications/@ml/*— other ML pipelines (rag-retrieval, content-moderation, message-classifier, prospect-classifier-claude, cot-reasoning, draft-pipeline-claude, etc.)~/Code/@applications/@quinn-ios/— Swift iOS app
5. Schema additions for tenancy
-- New tables in quinn.db (renaming to platform.db pending)
CREATE TABLE orgs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
owner_id UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE org_members (
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (org_id, user_id)
);
CREATE INDEX idx_org_members_user ON org_members(user_id);
-- Existing tables gain optional org_id where data can belong to either Person or Org:
ALTER TABLE bookings ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
ALTER TABLE brands ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
ALTER TABLE analytics_events ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
-- (When org_id IS NULL, the row belongs to the Person via user_id)
Seed for V3 launch
-- Bootstrap Quinn + Cocotte
INSERT INTO users (id, slug, ...) VALUES ('<quinn-uuid>', 'transquinnftw', ...);
INSERT INTO orgs (slug, name, owner_id) VALUES ('cocotte', 'Cocotte', '<quinn-uuid>');
INSERT INTO org_members (org_id, user_id, role)
VALUES ((SELECT id FROM orgs WHERE slug='cocotte'), '<quinn-uuid>', 'owner');
SSO JWT extension
interface SessionToken {
user_id: string;
device_id: string;
// NEW: optional org context (set when user switches into org view)
org_id?: string;
org_role?: 'owner' | 'admin' | 'member';
}
UI: context switcher in provider-portal nav toggles between Personal and each Org the user is a member of. Switching emits a new JWT scoped to that context.
6. Feature Composition for V3
Carry from v2 directly (working code, just rename + org-context)
21 features: sso, api, landing, my→provider-portal, provider-website→provider-site, cocotte-web+sansonnet-web→org-site, adult-therapy-tours→tour-site, messages, quinn-ai→ai-assistant, quinn-messenger→messenger, comm-newsletter→newsletter, user-data→org-analytics, admin→platform-admin, age-verification, client-intel, image-protection, vip, merchant, hotel-scout→tour-scout, price-watcher, edge-purge, db-monitor, platform-seed, waitlist, ui-dev-content, ai-engine, mail-autoresponder
Mine from v1 (production-quality code worth porting)
Phase A (foundations for multi-provider): marketplace, profile, attributes, bookings, trust, queue-worker, feature-flags, bot-defense, media
Phase B (depth & monetization): payments, reviews, threat-intelligence, safety, cms, blog, streaming, health-verification, content-moderation, image-generator, webmap
Mine from v0 (older but unique)
onboarding flow, mobile-messenger patterns (if pursuing a native mobile build), drive / mobile-drive (if pursuing first-class file management), content-moderation ML lineage (Python-based)
Superseded — don't port, but don't dismiss either
The following were genuinely novel and useful work in v0/v1 that has since been superseded by dedicated peer apps under ~/Code/@applications/@{ai,ml,imajin}/. The platform calls those peers over HTTP/MCP; the platform itself contains zero ML inference code.
| v0/v1 thing | Superseded by (peer app) |
|---|---|
knowledge-verification (v1, 262 tests, Crystal-AI's predecessor) |
~/Code/@applications/@ml/knowledge-platform/ |
conversation-assistant/ml-service (v1, 4.5G) |
~/Code/@applications/@ml/{assistant-trainer,chat,draft-pipeline-claude,message-classifier} |
i18n/ml-service (v1, 4.4G translation model) |
~/Code/@applications/@ml/ translation pipeline |
image-generator/ml-service (v1, 4.2G diffusion weights) |
~/Code/@applications/@imajin/ |
content-moderation/ml-service (v0+v1) |
~/Code/@applications/@ml/content-moderation/ |
talent-scout/packages/captcha-solver (v1, 3.8G) |
rebuild via @applications/@ml/ if needed |
| LLM gateway / agents (v0+v1) | ~/Code/@applications/@ai/{@agents,services,packages} |
Drop entirely
v0/v1 work that didn't pan out or was just placeholder: dating-autopilot, bio-scraper, linky/link-tree, pitch-deck, investor-dashboard, user-guide (move pitch-deck/investor/user-guide content into business/ non-code), and v1 empty shells consumable, content-editing, video-studio, share, favicon-generator, platform-content-tools, platform-assistant.
7. Naming Convention
| Concept | Old (Quinn-specific) | New (provider-generic) | Deployed domain |
|---|---|---|---|
| Provider dashboard | quinn.my |
provider-portal |
quinn.my (Quinn's instance), {provider}.my (others) |
| AI assistant | quinn-ai |
ai-assistant |
quinn.ai (Quinn's), {provider}.ai |
| Messenger | quinn-messenger |
messenger |
quinn.m (Quinn's), {provider}.m |
| Admin | quinn.admin |
platform-admin |
platform.admin (single, shared) |
| SSO | quinn.sso |
auth / sso |
sso.atlilith.com (single, shared root) |
| Analytics | quinn.data / user-data |
org-analytics |
{provider}.data |
| Tour booking | hotel-scout |
tour-scout |
(internal) |
Rule: Quinn's deployed quinn.* domains remain — they are Quinn's instance of the generic system. New providers get parallel {name}.* domains at onboarding time. The shared root (SSO, admin) lives at atlilith.com.
8. Migration Strategy
Sequencing principle: infrastructure first, features second. Paving the road (ports, DBs, proxy, tunnel, ACS, backups, CI/CD) is the highest-effort / lowest-rework work — and every feature lands on top of it. Lock it down before touching feature code. The infrastructure phase replaces both the old "Phase 4 skeleton" and "Phase 5 schema" steps because the two are coupled (compose files + ports + migrations + Caddy must all agree).
Phase 1 — Scaffold (DONE)
Create @atlilith/{@platform,.archive,tooling,.content-lifecycle}. Write DESIGN.md, INFRA.md, CLAUDE.md, README.md. Initial git + remote setup.
Phase 2 — Archives located, NOT vendored (DONE)
Prior versions are referenced where they already live; v0 is locally cached on apricot at ~/.cache/atlilith-archives/platform.0.tar.zst because its NFS source is slow. No archives in git. See .archive/ARCHIVED.md and scripts/{cache-v0.sh,extract-archive.sh}.
Phase 3 — Carry-overs (DONE, no-op)
tooling/ and .content-lifecycle/ from @lilith/ are empty stubs — already mirrored as empty directories. mail-sync and other peer services stay where they are.
Phase 4 — Monorepo skeleton (DONE)
@platform/{package.json, bunfig.toml, tsconfig.json}, codebase/tsconfig.base.json, empty codebase/@{apps,features,packages}/, deployments/@domains/, infrastructure/, docs/, scripts/.
Phase 5 — Infrastructure foundation (NEXT, must complete before any feature work)
Goal: a deployable, observable, recoverable system with all the glue in place. No feature code yet. When this phase ends, manage-apps start atlilith apricot brings up an empty-but-functional platform, deploys can be triggered via ./run deploy:<service>, and backups + tunnels run unattended.
5.1 Port registry & env
infrastructure/ports.yaml— adapted from v2; provider-generic service names (noquinn.*); new port ranges that don't conflict with v2 running in parallel (e.g. 3040-3059 APIs, 5210-5300 frontends, 25440-25445 PGs)infrastructure/.env.ports— committed env file in sync with ports.yaml; sourced by manage-apps
5.2 Databases
infrastructure/compose.platform-db.yml— main Postgres (port 25440)infrastructure/compose.platform-minio.yml— MinIO for object storageinfrastructure/pg-services.yml— PostgREST/service config if usedinfrastructure/platform-db-init.sql— base schema (adapted from v2'squinn-db-init.sql)infrastructure/sql/migrations/:001_add_orgs.sql—orgs+org_memberstables + owner-membership trigger002_seed_cocotte.sql— inaugural Cocotte org, transquinnftw as owner- (more as features land — every feature ships its own migrations)
5.3 Reverse proxy (Caddy)
infrastructure/Caddyfile.local— dev TLS for*.atlilith.apricot.laninfrastructure/certs/— self-signed certs (gitignored)infrastructure/gen-local-certs.sh— regenerates certs as services are added- Production: per-host Caddyfile templates under
deployments/@domains/<domain>/
5.4 SSH reverse tunnel (vps-0 ↔ black)
infrastructure/scripts/setup-tunnel.sh— establishesssh -R 25440:localhost:25440 -R 25441:localhost:25441 vps-0from black- systemd
user@.serviceunit + autossh for persistence + reconnect - Health-check script: vps-0 can
pg_isready -h localhost -p 25440 - INFRA.md §5 documents this tunnel direction (black-initiated for security)
5.5 ACS integration
users/transquinnftw/app.manifest.yaml—manage-appsservice registry for @atlilith (apricot + black + vps-0 platforms each with their services)- Verify ACS picks up @atlilith repo (it already commits; just confirm hooks/CI integration)
- Pre-commit hook for
pnpm typecheck/bun linton changed files
5.6 Backups & DR
infrastructure/scripts/backup-pg.sh— nightlypg_dumpallfrom black → restic repo on apricot tankinfrastructure/scripts/restore-pg.sh— counterpart with --dry-run guard- MinIO replication: vps-0 (hot) → black (cold) via
mc mirrorcron - Forgejo daily mirror push to GitHub (existing org policy)
- Verification: end-to-end restore drill into a scratch DB on apricot
5.7 Build / deploy pipeline
runshell entrypoint at repo root (./run dev,./run dev:stop,./run dev:status,./run dev:logs,./run build,./run deploy:<service>).forgejo/workflows/:typecheck.yml— runs on every push (bun install && bun run typecheck)lint.yml— runs on every pushdeploy-template.yml— reusable workflow each feature can call withwith: service: provider-portal
- Per-service
deployments/@domains/<svc>/deploy.shtemplate — rsync to vps-0/black, systemd reload, smoke test
5.8 Verification gates (must all pass before Phase 6)
manage-apps start atlilith apricotbrings up DB + MinIO + mailpit cleanlymanage-apps status atlilith apricotreturns healthy for every service entrypsql -h apricot -p 25440 -U platform -c "SELECT * FROM orgs WHERE slug='cocotte'"returns 1 row- JWT-bearing curl through Caddy to a placeholder route succeeds (TLS works)
- vps-0 can connect to black's PG through the tunnel
- Backup script runs end-to-end (
backup-pg.sh && restore-pg.sh --to=/tmp/restore-test) - A trivial commit triggers
.forgejo/workflows/typecheck.ymland it passes
Files to read/carry from v2:
~/Code/@projects/@lilith/lilith-platform.live/infrastructure/{ports.yaml,.env.ports,Caddyfile.local,compose.*.yml,pg-services.yml,quinn-db-init.sql,gen-local-certs.sh,setup-*.sh}~/Code/@projects/@lilith/lilith-platform.live/users/transquinnftw/app.manifest.yaml~/Code/@projects/@lilith/lilith-platform.live/run
Phase 6 — V2 carry-over wave (features start here)
Port the 21 "carry directly" features from v2 (see §6). Rename internal package names (quinn-ai → ai-assistant, etc.). Verify deployment to existing quinn.* domains still works through the new infra (no drift).
Phase 7 — Context switcher UI
Provider-portal nav: transquinnftw ▾ dropdown → [ Personal | Cocotte ]. Org view shows member roster, org-level analytics, brand settings.
Phase 8 — V1 mining (Phase A foundations)
Marketplace, profile/attributes, bookings, trust, queue-worker, media, feature-flags, bot-defense. Adapt to org-aware schema.
Phase 9+ — V1 mining (Phase B depth)
Payments, reviews, streaming, content-moderation, threat-intelligence, etc. — sequenced by demand.
Phase 10 — Deprecation
Once V3 is at parity + org-aware: cut over quinn.* domains to V3, decommission V2 deployment. Archive V2's running infra.
9. Open Questions
- Operating host: should
@atlilithlive on apricot only, plum only, or both (synced)? Default assumption: develop on plum, push to apricot for deploy. - v0 variants: archive only
egirl-platform/asplatform.0, or also include-worktrees/,-releases/,-partial-backups/,egirl.vault/as.archive/platform.0-variants/? @messengervsmessagesfeature:~/Code/@applications/@messenger/exists separately from v2'smessagesfeature. Are they the same code? Different concerns? Needs reconciliation before V3 messaging starts.- knowledge-platform lineage: v1 had
features/knowledge-verification(262 tests, Python).crystal-cli(Feb 2026) was a CLI wrapper.@applications/@ml/knowledge-platformv2.0.0 is the current successor. Confirm: is this dead code or active? Should it stay peer, or fold into V3? - Mining sequence: which V1 feature lands first in V3 — marketplace (multi-provider discovery), profile/attributes (provider profile depth), or trust/safety (verification before opening up)?
- Client surface: when V3 launches, do clients have accounts (logged in to view bookings, message providers), or stays anonymous-on-request?
10. Success Criteria
V3 is considered shipped when:
- ✅ Quinn's
quinn.*deployments are running on V3 code (no V2 infrastructure left) - ✅ Cocotte exists as an Org with Quinn as owner; org context switcher works in provider-portal
- ✅ A second provider (e.g., merche biche) can be onboarded as a Person + optional Org without code changes
- ✅ All 21 V2-carry features work in V3
- ✅ At least one V1-mined feature (likely marketplace OR profile) is live and providing value beyond V2
- ✅ V2 deployment is shut down;
.livedirectory becomes purely historical (.archive/platform.2/)
Appendix: Sources
- v0 source: apricot:
/mnt/bigdisk/_/last-linux-backup/applications/src/@egirl/egirl-platform/(27 apps, 23 services, 14 packages, viky-era) - v1 source:
~/Code/@projects/@lilith/lilith-platform/(54 features, 38 packages, 72GB, never deployed end-to-end) - v2 source:
~/Code/@projects/@lilith/lilith-platform.live/(24 features, 7 packages, 27 live domains) - Peer apps source:
~/Code/@applications/{@mac-sync,@messenger,@agents,@ml,@quinn-ios}/