Platform

A control plane for AI identities in private communities

CIND3R3LLA runs the embedded SimpleX core, the application logic, the local models, the archive and the administration as one process on infrastructure you control. The consent first archive is live today, and the multi profile identity runtime is being built on the same foundation.

More than a chatbot

CIND3R3LLA is a self hosted control plane for intelligent identities inside private and public communities. The embedded SimpleX core, the deterministic application layer, the moderation workflow and the public archive run as one process on infrastructure the operator owns, with the local language models on a private endpoint on the operator's own machine.

A chatbot answers messages. A control plane decides who may act, in which group, with which permissions, under whose supervision, and what is allowed to become public. In CIND3R3LLA those decisions are application code and database state rather than prompt text, and they hold whether the identity in front of them is a person, an assistant or a scheduled character.

The archive is the first capability built on that foundation, and it is running. A bot joins a public SimpleX group, captures the messages of members who opted in, stores them in PostgreSQL with a media tree on disk, and republishes them as a searchable public site. Text, images, video, voice, files, links, edits and in group deletions all travel that path.

The identity is not the implementation. Whether an avatar looks like a fairy tale character says nothing about whether a human is behind it, how much it may automate, or what it is permitted to do. Those are separate settings in separate places, and transparency belongs in onboarding, welcome messages, terms and the public profile rather than in a label stamped onto every sentence.

A chatbot answers messages. A control plane decides who may act, and what may become public.

Identities under human control In development

One embedded core, many identities. The next runtime replaces today's single bot wrapper with a shared multi profile core: one ChatApi.init(), one startChat(), every profile subscribed at the same time, incoming events attributed by the receiving userId, a local group identity of userId plus groupId, commands that depend on the active user serialized instead of raced, outgoing messages recorded from the command result, and no profile rotation in normal operation. Those are measured properties of the official SimpleX Node SDK, not assumptions, with one exception recorded as untested: the degraded state was reasoned about rather than measured, because network interruption was never exercised.

Readiness is a state, not a return value. The runtime will distinguish offline, starting, subscribing, ready, degraded and stopping. A bot is not ready merely because startChat() returned. Subscription progress and operational readiness are two different facts, and the administration will show the one that matters.

Four actor types are designed to carry the permissions: a real member identity, a human operated agent (moderator, administrator, support specialist or recurring character with a responsible person behind it), an NPC (game host, quiz master, storyteller, tutorial guide, welcome character), and system automation for technical notifications. The classification is what will drive permissions and automation, with two invariants written into the design before any code: a supervised identity must never be silently converted into an autonomous one, and human takeover must be immediate. None of this is built yet: the database today carries profiles, groups, authorities and roles, not actor types.

Part of this already exists in the database. Profiles, groups and authorities are tables, with the roles owner, administrator, moderator, team member, member, auditor and blocked. Each profile carries a hard privacy baseline, local only or cloud allowed, that a database constraint will not let contradict itself. A deterministic policy resolver maps an incoming group message to profile, group, role and baseline, and that resolver explicitly executes nothing: no remote commands, no persistent changes, no invitation links. A deterministic name generator is in the tree as well, because generated identity has to work without a model before a model is allowed to improve it.

Groups are typed as team, member or test, so an agent can be discussed, corrected and trained in a private staff space before it acts in public. The engine that gives NPCs rhythm, cooldowns and silence during sensitive moderation moments is specified and not yet built.

The system must never silently convert a human operated identity into an autonomous NPC.

Consent first memory

One gate is absolute: each member opts in for themselves, and nothing they post is public until they do. The operator decides whether an archive front exists at all and which categories may appear, but no operator setting can ever open the member's gate for them. A member opts in by sending /publish, or by asking in plain language and confirming when she asks back. Consent is always first person. Nobody can grant it on somebody else's behalf.

Only messages sent after the opt in are eligible, and eligibility is derived rather than stored as a flag. The publication views compute it from the consent record, the send time, deletions, the moderation state and the gaps that a hide and restore cycle leaves behind. A stale boolean cannot outlive the decision it once described.

Withdrawal hides everything at once. The member then chooses: hide, which retains the material so that they alone can restore it, or delete, which erases it. Hiding is never deferred. Where a legal preservation obligation applies to a specific item, only the physical erasure waits, and it runs by itself once the hold is released or expires. A restore never republishes what was said while hidden, because the gap is recorded. Text, images, video, voice, files, links, edits and in group deletions all follow this same route.

Nothing a member posts appears on the public archive unless that member opted in.

Local intelligence, deterministic authority

Inference runs on a private Ollama endpoint. The configuration loader refuses to start when that endpoint is anything other than localhost or a private address. Credentials in the URL are rejected, a path or a query string is rejected, and a public endpoint produces a startup error stating that public AI endpoints are disabled. Conversation content staying inside the building is enforced by a process that will not boot, not by a promise.

Turning local AI on is fail closed. The runtime probes the endpoint before it activates, reads the installed models with their family, parameter size, quantization and file size, and refuses to activate or route to a model that is not installed. Intent classification and reply wording are routed to separate models, so a small classifier and a larger writer can coexist. Every runtime and routing change is audited, and the operational telemetry counts requests, latencies and outcomes without recording content.

The model has no authority. It classifies text and it improves wording. It never executes an action, writes consent, calls a tool or decides whether a confirmation was accepted. Consent intents carry an extra deterministic gate: the model may confirm publish or unpublish only when the rule based resolver independently found the same intent. When the model is slow, wrong or switched off, the deterministic path answers, and the fallback is counted and shown in the administration instead of quietly disappearing.

AI may classify and phrase. Identity, permissions, consent, routing, publication and execution stay application controlled.

One transport today, a seam for the next

SimpleX is the transport, and it runs as the official native core inside the same Node process. There is no external CLI daemon, no remote control layer and no exposed chat port. The application owns the event loop, the local databases, file reception, capture and the outgoing path, and the sensitive surface is the on disk database protected by filesystem permissions.

The SDK sits behind a single seam. Cinderella has her own domain types and one chat adapter interface. Exactly one directory may import the chat library, an automated check fails the build if anything else does, and that check proves it fails on a violation rather than passing quietly. A fake adapter drives the whole interface with no SDK present, which is how the seam gets tested instead of asserted; the public demo runs on that fake adapter today, while the live SimpleX path runs through the one directory allowed to import the SDK.

A second transport is prepared, not written. The adapter contract already names the two places where a different network has to make a decision: the opaque raw item, whose stored JSON is read by SQL today, and the SimpleX model of a private thread inside a group, which has no direct equivalent elsewhere. Until that adapter exists, SimpleX is the only transport in the code.

Exactly one directory may import the chat SDK, and a check fails the build if that stops being true.

Content screening: custody built, detection not connected In development

Custody is built. Originals are encrypted at rest with AES-256-GCM under a dedicated key, every reader of an original goes through one module, and quarantined material is moved out of the media tree into a separate root that nothing serves. The administration addresses media by message id, never by path. A match preserves and quarantines. It never deletes.

Detection is not connected. No screening provider is configured, the null provider transmits nothing, and a fixture provider proves the quarantine path in the verification harness with no real material involved. Once a provider is connected, hash matching finds known material only, never new material, and a no match is not a statement of safety.

Reporting duties, retention periods and the point of contact are legal questions for a lawyer. They are deliberately absent from the code rather than guessed at.

No screening provider is configured, and the public copy says in development until one is connected and verified.

The architecture, in words

The diagram beside this reads from top to bottom, and so does the process. The SimpleX network reaches the embedded native core inside the application. The SimpleX network reaches the embedded native core inside the application, and files are received by the application itself. A durable write-ahead store for incoming events is built and verified, and wiring the dispatcher to record every event before it is applied is the next step; per profile attribution of incoming events arrives with the multi profile runtime.

Below the core sits the deterministic application layer, and everything that decides anything lives there: identity and permissions, consent, routing, moderation, the interaction engine with its plugins, the archive and the public site, human supervision and audit. This layer is ordinary code and database state. It is readable, testable and reviewable, which is the entire point of putting it there.

Below that, and only below that, sits the private local AI: intent classification, reply wording, and later optional profile text and avatar generation. It can support a decision the application defined. It cannot grant itself authority.

Two logical databases are kept apart. The SimpleX core keeps its own local state. Cinderella keeps the archive in PostgreSQL with messages, links, consent, settings, audit and embeds, and full text search as a generated tsvector with a GIN index. Media lives on disk and the database stores the path, never the bytes. The original is encrypted, the public derivative is stripped of metadata and stays plaintext, and the two roots are not allowed to be nested inside one another.

Background work runs on a durable Postgres job queue with a state machine, claims taken using FOR UPDATE SKIP LOCKED, backoff, a dead letter state and idempotency keys, so a restart loses no obligation.

The administration is a Fastify service bound to the loopback interface, with public TLS in front of it at the admin hostname. Passkeys are the primary authentication, with an operator toggleable break glass password behind Argon2id and optional TOTP. Sessions are signed, HttpOnly, SameSite strict and stored in PostgreSQL so they survive a restart, CSRF is checked on every mutation, and proxy trust is pinned to loopback.

One process, two logical databases, media on disk. The database stores the path, never the bytes.

The administration principle

Every operational capability should have eight things. This is the rule the administration is measured against, and it is why the console is large.

A backend implementation. A control exists because behaviour exists behind it. A control exists because behaviour exists behind it, and nothing important is buried in code with no control at all. Where a surface is deliberately ahead of its backend, it ships visibly disabled and labelled as not yet active rather than looking operable.

Persistent settings. What an operator changes is stored in the database and survives a restart. Environment variables carry secrets, deployment facts, and the few decisions an operator makes once when provisioning the machine, such as whether a local AI endpoint exists at all. Everything an operator changes while running the product lives in the database and survives a restart.

An administration control. Every runtime decision has a page: content and moderation, consent, interaction and wake words, the local AI runtime, the model catalog and routing, hardware facts, security, plugins, media, evidence holds, reports, screening and the public site.

Stored and effective status. What is configured and what is actually running are shown as two separate facts, because they do diverge. Local AI enabled in the settings with an unreachable endpoint is a state the operator needs to see, not a checkbox that lies.

Audit coverage. Every state changing administrative action is recorded with actor, action, target, time and detail, including consent actions taken on a member's behalf, which additionally carry their provenance and a way back.

Automated tests. The verification harnesses run against a real PostgreSQL compiled to WebAssembly, with no server required, and they cover consent, revocation with evidence holds, the publication views, security, the public site, interaction, the queue, capture events, encryption at rest and the screening seam, plus the adapter seam and the rules for member facing copy.

Clear failure behaviour. A caught error is never converted into a value that reads like a legitimate result, and a degraded function never runs silently. On the consent, capture, publication, media and plugin paths, a lost guarantee is raised to the dashboard rather than to a log file nobody reads. Not configured and configured but failing are different states, and a fallback that could mask a fault is counted with the count on screen.

Documented boundaries. Five technical documents, architecture, security, wire format, decisions and backlog, are maintained from the code with every change rather than at the end of a season, and what is implemented is kept visibly apart from what is planned.

CIND3R3LLA does not hide important behaviour in code while presenting an empty control panel, and it does not present controls that are not connected to real backend behaviour.