One embedded core, many persistent identities
CIND3R3LLA runs its identities inside one embedded SimpleX core instead of starting a process per bot. This page describes how that works, what already sits in the live path, and what is configuration still waiting for the runtime.
The core is embedded, and it runs in one process
CIND3R3LLA loads the official SimpleX chat core directly into the Node.js process through the simplex-chat package. There is no separate daemon, no exposed SimpleX port and no remote control channel. The application owns the event loop, the local SimpleX database, file reception, message capture and the outgoing send path.
The core keeps its own state in SQLite, in a directory protected by filesystem permissions. Everything CIND3R3LLA itself remembers, meaning messages, links, consent, settings and audit, lives in a separate PostgreSQL database. The two are deliberately not merged.
Today that core starts with exactly one profile. The display name comes from configuration, no public contact address is created, file transfers are accepted, and the stored profile is only reconciled when an avatar file was actually read, so a missing avatar file can never blank the identity a group already sees.
The sensitive surface is a local database file, not a network port.
Many profiles inside one core In development
The next runtime replaces the single bot wrapper with a shared multi profile core. The design follows measured behaviour of the SimpleX Node SDK rather than assumption: one ChatApi.init(), one startChat(), every profile subscribed at the same time, commands that depend on the active user serialized so two identities cannot interleave a user switch, and outgoing messages recorded from the command result rather than from a hopeful echo. Profiles are not rotated in normal operation.
A profile is a SimpleX user inside the same core, not a second copy of the application. That is what removes the heavy costs of a bot fleet: no second chat core, no second local database and no second process to supervise. What it does not remove is reconciliation, because one conversation reaches every participating profile under its own ids, and mapping those onto a single conversation identity is open design work rather than finished work.
What runs today holds exactly one user handle, and every call that needs a user passes that one identifier. The command scheduler and the subscription tracking described here are the work in progress, not the running system.
What is multiplied are identities, not processes.
Attribution runs over the receiving identity In development
As soon as several identities share one core, every event has to answer which of them received it. SimpleX answers that with the receiving user id, so attribution is read out of the event and never guessed from the group name or the sender. A SimpleX group id is globally unique inside a core, but it identifies a membership rather than a conversation, so one real conversation yields a different id for every profile that joined it. That is why the honest key is the receiving identity plus the group, and why several memberships have to resolve to a single conversation identity.
Today one identity receives everything, so the schema keys a group on the SimpleX group id alone and enforces that a group id belongs to exactly one configured group. Widening that key, and carrying the receiving identity through the captured message and the send target, is part of the multi profile work rather than a detail left to be discovered later.
Both changes land behind the adapter seam, the interface that separates CIND3R3LLA from the protocol library underneath. Only the adapter may import the SDK, and the check that proves it also synthesises a violation and asserts that it is caught, because a guard nobody has seen fail is a guard nobody knows works.
A group id means nothing without the identity that received it.
Started is not the same as ready In development
The design distinguishes six states: offline, starting, subscribing, ready, degraded and stopping. A bot is not ready merely because startChat() returned. Subscription progress and operational readiness are separate facts, and a fleet that reports readiness too early sends into groups it has not finished subscribing to.
Degraded is meant to be a real state rather than a polite word for fine, and it is the one state in the set with no measured basis yet, so it will ship labelled untested until a network interruption has actually been observed. The standing rule across this project is that a failure is surfaced rather than swallowed, and a degraded identity belongs on the admin dashboard, not only in a log file. Today the console reports the running bot and its groups and raises explicit errors for faults such as a files folder that could not be configured. The six state machine itself is designed and not yet in the code.
A bot that reports ready before it has subscribed is a bot that loses messages.
The policy layer is already in the live path
Profile and group policy is not a plan. Every ordinary incoming interaction passes through the runtime policy resolver before the interaction engine sees it: the resolver decides, records the decision, and only then calls the engine. A denied message never reaches the engine. Consent commands are the one deliberate exemption, because a blocked member must still be able to withdraw.
Resolution is one query that joins the group to its profile and to the sender's authority entry. The outcome is allow, deny or unassigned, always with an explicit reason: allowed, group unassigned, profile disabled, group disabled or member blocked. Every decision is written to its own table, keyed on the group and the message item, so a redelivered event updates one row instead of growing a journal of duplicates.
A group nobody has assigned yet resolves as unassigned: interaction stays allowed, cloud use stays off, and the decision records that no enforcement was applied. Adding the policy layer therefore did not silently switch off a group that was already working.
Two fields are types rather than settings: remote commands and persistent changes are typed as permanently false, so no row in any table can turn them on.
Profiles, groups and authorities
A profile carries a slug, a display name, an enabled flag, a local only flag and a cloud allowed flag. A personality column exists alongside them, but nothing writes it yet, so every profile currently holds the same default. The slug rule, 2 to 63 lowercase letters, digits or hyphens, is enforced in the code and again as a database constraint, and the database refuses a profile that is both local only and cloud allowed.
Groups are typed as team, member or test. A profile has at most one team group, enforced by a partial unique index rather than by application discipline, and one SimpleX group can be assigned to exactly one configured group. At the profile level, a profile has at most one enabled owner, enforced by the same kind of partial unique index.
Inside a group a member can be owner, administrator, moderator, team member, member, auditor or blocked. Management capability belongs to the first three. Team guidance is granted only inside a team group, so an ordinary community group cannot become a back channel for changing how an identity behaves. A member with no entry resolves to member, and the decision records whether that role was assigned or defaulted. Every creation and every role change writes an audit row, and enabling or disabling a profile or a group does too. Disabling an individual authority is not wired up yet, so the enabled flag on an authority is currently set at creation and not changed.
Local only wins. If a profile is marked local only, the resolver forces cloud use off regardless of what the cloud flag says.
The bot registry stores intent, it does not act In development
A second table holds the desired onboarding configuration for a bot identity: whether an address is created or updated, whether the profile is updated, whether contacts are accepted automatically, the welcome message, whether files are allowed, the command registry mode, how group invitations are handled (manual, automatic, approved contacts or approved groups), the expected role in the group, whether that role is verified before the profile is activated, retention windows for pending contact requests and invitations, and a ceiling on how many requests may be pending.
The service that owns this table never calls the SDK, and it says so in its own audit trail: every write records that the runtime was not applied, and deleting an entry records that no SimpleX identity was deleted and no group membership changed. The workflow states from configured through joined and role verified to ready are declared, and only the reset transition is implemented. The rest move when the runtime applies the configuration to a live core.
Configuration that has not been applied is recorded as not applied. The audit trail does not flatter it.
Four actor types, and what a label is for In development
The identity model will separate a real member, a human operated agent, an autonomous NPC and technical system automation. The distinction is designed to control permissions and automation, and deliberately not to be stamped onto every message and avatar. Transparency belongs in onboarding, in the welcome message, in the terms, in the profile information and in a public directory of identities, where a person can actually read it. A fantasy avatar says nothing about whether a human stands behind the identity.
The rule that matters most is a negative one: the system must never quietly convert a human operated identity into an autonomous one. Today permissions are expressed as the profile and group roles above, and the actor type taxonomy arrives with the controls for human operated agents.
The system must never quietly turn a supervised identity into an autonomous one.
Names for generated identities In development
A deterministic name generator is built and verified as a standalone component. A seed plus a configuration produces a name, the same seed produces the same name again, the draw is Zipf shaped so a few names recur and most are rare, and the result is sanitised so it is valid as a SimpleX display name. It touches no database, no runtime and no network.
It is not yet wired into profile creation, and two advertised properties are not delivered: the shipped corpus carries no culture labels, so the culture grammar currently runs against small hand written fixture pools, and it carries no frequency data, so which names come out common is arbitrary rather than drawn from a real population. Culturally coherent names arrive with a labelled corpus, and the swap point is marked in the code rather than left implied.
What this means if you are running a community
For an operator the practical question is what a second identity costs. For an operator the practical question is what a second identity will cost. The design answers a row and a subscription rather than a second server, and today one identity is what actually runs, so treat that answer as the target rather than as something you can provision now. Assignments are stored against stable SimpleX identifiers, so renaming a group in the client does not detach its policy.
Consent identity is unaffected, because consent is keyed on the member id the protocol gives every profile alike, and no profile, role or policy row can publish on a member's behalf. How publication behaves when several identities share one group is still open design work, and it is being settled before any second identity goes live. Publication stays derived from what a member opted into personally, and no profile, role or policy row can publish on a member's behalf. Identity decides who may speak and with what authority. It never decides what becomes public.
A second identity costs a row and a subscription, not a second server.