One codebase serves every vertical. At the center is an event-sourced core — every write is an append, folded into CQRS projections and a business knowledge graph, replayable to any past state. The runtime is config-driven: a tenant’s objects, fields, and views are versioned data, not per-vertical code. Over that stream, five specialized AI agents observe, advise, build, guard, and learn — always behind mandatory human gates. The rest of this brief is each of those pieces, with the file that implements it.
append-only log, replayable to any past state
observe · advise · build · guard · learn, human-gated
one PostgreSQL, RLS on 21 of them
one Hetzner AX102, host-level Caddy TLS
Every domain write funnels through a single helper, emitEvent(), so the fold-out into read models is identical everywhere. It appends the row to the append-only events table — the source of truth, never UPDATEd or DELETEd — then folds it synchronously into the CQRS projections and, fire-and-forget, into the knowledge graph and the Observer trigger. Because the log is the truth, any projection can be rebuilt by deterministic replay: POST /api/events/replay re-reduces the stream in(created_at, id) order, and projection_checkpoints stores the high-water mark so replay is resumable and drift is self-healing.
The synchronous projection fold is wrapped in try/catch: a projection bug can never fail an event write. Drift is repaired by replay, not by blocking the append.
Four projections are derived, deterministic, and safe to TRUNCATE: entity_activity, agent_activity, tenant_event_stats, and the projection_checkpoints replay marker. Nothing writes there except the engine.
The event stream is projected — async, fire-and-forget — into kg_entities and kg_relationships. Entity resolution keys on (tenant_id, entity_type, natural_key), so the same business object seen across many events resolves to one row via upsert-merge. Every node carries source_event_id for provenance back to the log and a confidence score, so the graph can hold uncertain, AI-derived facts alongside hard ones. Traversal is a recursive CTE bounded at MAX_DEPTH = 5 — deep enough to reason across a business, bounded so a query can’t walk forever.
There is no per-object table and no per-vertical codebase. A tenant’s objects, fields, stages, and views live in tenant_configs — a versioned TenantConfig where one active version drives the whole rendering engine and older versions are retained for rollback. The generic records API at /api/records/[objectKey] reads and writes kg_entities by key, and the engine renders table, kanban, or detail per the active config. Onboarding is either 10 templates or an AI-describe flow that generates the config from a plain-language description of the business.
The wedge (franchise, CRM, …) was removed as code in the de-wedge refactor (migration 005); those objects now live as tenant data in kg_entities. Change the description and the runtime reshapes — no rebuild, no migration project.
A per-tenant MCP endpoint, exposing each tenant’s objects to external AI agents over the Model Context Protocol, is a roadmap item. Designed Today the standard plugs are the records API and the event bus.
There is no single opaque “AI” — five narrow agents each have one job. What runs on the live loop today is the Observer cycle: ObserverAutoTrigger fires on a 50-event threshold or a 5-minute timer per tenant (whichever comes first) and runs an analysis pass — detect patterns, enhance them with an AI call, and write the insights that become the decisions in your briefing. A person acts on each one, and those outcomes feed trust-tier advancement.
The fully chained Observer → Advisor → [human] → Builder → [human] → Guardian → Learner pipeline, with per-action gating on each agent’s trust tier, is built in the codebase but not yet wired into that live loop. Designed
There are two mandatory human gates — after Advisor and after Builder — and nothing the AI proposes reaches deploy without a human yes. The Builder never auto-applies: it generates code and config in an isolated sandbox, and its pipeline triggers only on anapproval_granted event. Builder output needs dual control: the human gate and the trust-tier gate must both clear. Approve, and the change ships and is recorded as an event; reject or send back, and the proposal returns to the agent with your note — nothing was applied while it waited.
Guardian’s scan reads an agent output for PII exposure, prompt injection, and permission-boundary breach, deterministic-first (regex before any AI call). On severitycritical or high it can block the change; lower severities it flags. Today it runs through its own endpoint and is gated by GUARDIAN_AUTO_ENABLED (off by default). Wiring it to screen every proposal automatically — an inline gate no raised trust tier can remove — is built but not yet on the live path. Designed
The trust_tier enum runs observe → suggest → automate → expand → replace, set per class of change and evaluated from the Observer cycle. Advancement is earned: it needs human-rating samples above a minimum average, a 14-day cooldown, and passing adversarial tests; failures and Guardian blocks demote. Crucially, this is not the billing axis — trust tier governs autonomy and is entirely separate from the SubscriptionTier system (free / starter / pro / enterprise) that gates features and usage. IT can override, demote, or emergency-freeze any agent to observe-only in one action, no redeploy.
Agents watch and report. Nothing acts.
Ranked, costed recommendations. No changes made.
Approved change classes run on their own — Guardian still watching.
Autonomy widens to adjacent decisions in the same class.
The agent owns the workflow end to end. Exists in the enum; not exercised in production.
observe · suggest · automate · expand are shipped. The fully-autonomous replace tier exists in the enum but is not exercised in production — it is a designed ceiling, not a live default.
The same change travels one circuit seen from two sides. A plain-language question in the Business Console becomes a proposal an agent makes; an IT reviewer approves it in the Control Plane before it ships. Two audiences, two screens, one loop — ask, propose, approve, ship — with the five agents sitting between the surfaces and the event log recording every hop.
Rip-and-replace throws away history every few years — the assumptions, the edge cases, the reasons a thing was built the way it was. LiquidSilicon keeps all of it: because the core is an append-only log, nothing is lost and everything replays. Each shipped change is graded by the Learner against what actually happened, and that outcome sharpens the next proposal. The software does not reset to zero every three years; it compounds — every prior decision is still on record, still queryable, still feeding the loop.
The platform runs as 11 Docker Compose containers on a single Hetzner AX102 (Ryzen 9 7950X3D · 192GB · 3×1.92TB NVMe). Cloudflare DNS points at host-level Caddy — not a container — which terminates TLS and sets security headers in front of adaptive-app:3456.
| Container | Role | Port |
|---|---|---|
| adaptive-app | Next.js 15 / React 19 — the app | :3456 |
| adaptive-db | PostgreSQL — pg16 prod / pg17 dev | 5432 |
| redis | Cache + rate-limit | 6379 |
| nats | JetStream event bus | 4222 |
| prometheus | Metrics scrape + store | 9090 |
| grafana | Dashboards — loopback only | 127.0.0.1:3301 |
| alertmanager | Alert routing | 9093 |
| nats-exporter | NATS / JetStream metrics exporter | 7777 |
| postgres-exporter | Database metrics exporter | 9187 |
| redis-exporter | Cache metrics exporter | 9121 |
| maddy | SMTP — transactional mail | 25 / 587 |
Prod / dev divergences to remember: PostgreSQL is 16 in prod, 17 in dev — reproduce prod behavior with care. The app is pinned to dev port 3456 from the shared port registry. Grafana is loopback-only (127.0.0.1:3301), never publicly exposed.
RLS enabled + a tenant-isolation policy keyed on the app.current_tenant_id session variable (migrations 001–006). tenant_ai_config and ai_spend_ledger were brought under RLS in the 006 BYOK-hardening pass.
Not DB-enforced — these depend on a correct WHERE tenant_id = … in the service layer. tenants is the tenant table itself; projection_checkpoints is a global replay marker keyed by projection name, not tenant.
On the roadmap, not in code Designed — runtime schema evolution (agents mutating a tenant’s active config live), a 24/7 autonomous agent swarm, generative agent-synthesised UI, and the per-tenant MCP endpoints. Configs today are versioned and swapped deliberately; the renderer is config-driven, not generative.
The architecture is the machine; the product brief is what it feels like to run. Point LiquidSilicon at your operation, watch the agents work with zero autonomy granted, and raise the dial only when they have earned it.
Observe-only is free · Humans approve every change