Technical specs · Soma platform
The anatomy of Soma,
in engineering terms
How the Soma platform is built: the full stack, how a request moves through it, how data is stored and kept apart, how each layer is secured, and where AI does the work. Written for engineers and technical evaluators. The plain-language overview tells the same story with the biology left in.
soma/ one body: sense → integrate → act → remember
/act01 · ArchitectureHow it fits together
Read the schematic top to bottom as one request's journey. A request enters through Slack or a browser, crosses the only public surface (Caddy), and lands on the Portal or the Thalamus. Agent work is queued, picked up by a stateless worker that reasons with a model behind a privacy-aware gateway, and every effect it wants goes through the Action Layer: the single gate that writes to the Cortex and dispatches to the outside world. Results come back as events.
Swipe sideways to see the whole schematic →
One Linux server runs everything above except the model providers and the SaaS tools at the bottom. The services share an internal network with no route to the internet. Only the services that must reach outside (Portal, Thalamus, agent workers, Action Layer) and the proxy also join an egress network; the database has no way out at all.
02 · StackThe full stack
Eight containers from one compose file, plus one host service. There is no ORM anywhere: every service speaks plain SQL to Postgres, so what runs is what you read.
| Component | Technology | What it does |
|---|---|---|
| Edge | ||
| Proxy | Caddy 2 | TLS for every host (automatic certificates for GSC hosts; a Cloudflare origin certificate for the venture domain). Reads the hostname and routes to the Portal, the Thalamus or n8n. The only published ports. |
| Portal | SvelteKit 2 · Svelte 5 · Node | The whole product surface: dashboards, client register, agents, admin, audit, the Synapse chat. Tailwind with daisyUI. Reads the Cortex with a read-only login; sends every write to /act. |
| Thalamus | Python 3.12 · FastAPI | Slack Events API, slash commands and interactive payloads. Verifies Slack's signature, de-duplicates events, picks the agent, writes a task, posts the reply. Holds no write token and no model key. |
| Reasoning | ||
| Agent workers | exec-agent ×2 · Python 3.12 | Identical, stateless replicas. Claim a task, load the seat (persona, grants, skills, packs) from the Cortex, retrieve, run the tool loop, render documents to PDF in-process. |
| LLM gateway | TrustedRouter → OpenRouter | OpenAI-compatible routers. TrustedRouter is primary and carries a minimum-privacy setting on every request; OpenRouter is the fallback member. Which model serves is data, set per class by an admin. |
| Embeddings | text-embedding-3-small | 1,536 dimensions. Knowledge-pack chunks are embedded when they are ingested; questions are embedded when they are asked. |
| Action and memory | ||
| Action Layer | Python 3.12 · FastAPI · psycopg 3 | The single write path. A registry of typed action kinds, each with a JSON Schema (Draft 2020-12) and a policy, plus one handler module per family. Writes the event and the state change together, then dispatches. |
| The Cortex | PostgreSQL 16 + pgvector | 111 tables of canonical state, an append-only events log, the task queue and the knowledge-pack vectors (HNSW, cosine). Row-level security is enabled and forced on tenant data. |
| Schema changes | Plain SQL, up and down | 250+ numbered migrations, each paired with a verify script that proves it landed. No migration framework: each one is applied through a gate and verified against production. |
| Effectors and host | ||
| Automations | n8n | Multi-step workflows (lead-generation intelligence, error handling). Runs in the same compose file and holds no /act credential of its own. |
| Connections | API · webhook · MCP · CLI | Google Workspace, Slack, GoHighLevel, ClickUp, TeamCentral (over MCP), web research and others, each a governed connection. Each action kind declares which connection type it needs and which provider hosts it may reach. |
| Render sidecar | Node · headless Chromium | A standby HTML-to-PDF renderer on the internal network only, with no route out. Day-to-day PDFs are now rendered inside the agent workers. |
| Repo runner | systemd service | The executor behind the repository actions (push, deploy, rollback). Runs on the host, sandboxed by systemd, and every operation it performs needs a human co-sign. |
| Secrets | Bitwarden Secrets Manager | Secrets are rendered into the environment file at deploy time (owner-only permissions, previous copy kept). The repo holds key names only, checked nightly against what is configured. |
03 · Request lifecycleThe life of one Slack question
Someone mentions an agent in Slack and asks for something that needs both thinking and doing. The same path serves the Synapse chat in the Portal; only the first two steps differ.
- Slack delivers the eventsense
The Thalamus checks Slack's HMAC-SHA256 signature in constant time, rejects anything older than five minutes, and drops a repeated event id.
- The right agent is chosen AIroute
An explicit
@agentwins; otherwise a router model call picks one specialist agent the person is allowed to reach. The Thalamus writes a task to the queue and nothing else. - A worker claims it and loads the seatload
One of the two stateless workers takes the task and reads the agent's persona, version, grants, skills and knowledge packs from the Cortex.
- Retrieval AIintegrate
The question is embedded and matched against published knowledge-pack revisions by full-text and vector search together, merged by reciprocal rank fusion; the top six chunks go into context.
- The tool loop AIreason
The model answers with either a reply or one tool call as a JSON line. Up to six steps and seven minutes per run. Tool results come back inside fenced markers, with any forged markers stripped first.
- Every tool call is a governed actionact
Each call becomes a
POST /actas the agent, carrying the run's envelope. The Action Layer runs its full check (below). High-impact kinds are parked for a human to approve instead of executing. - Commit, then dispatchexecute
The event row and the state change are written in one transaction. Only after the commit does the handler reach the outside world: n8n, Google, the CRM, an MCP server.
- Results come homeremember
What the outside world returns is recorded as events too, so the Cortex never drifts from reality. The Thalamus posts the answer back to the Slack thread, and the run's trace and model usage are metered.
Inside /act: eleven checks, in order
Every write in the platform, from a person clicking Save to an agent's tool call, passes the same pipeline. A failure at any step stops the request with nothing written.
04 · DataHow data is stored and reached
One Postgres database is the single source of truth. Dashboards, agent context and reports are views of it, never copies that could disagree. Isolation is enforced by the database itself, not by application code remembering to filter.
State tables
111 tables: clients, projects, tasks, people, agents, skills, connections, reports, structure and access. Every tenant row carries its tenant_id.
Append-only events
Every change is a typed event naming what changed, when and on whose authority. The writer role has no UPDATE or DELETE on the table and its policies allow only reads and inserts, so history cannot be quietly rewritten.
Row-level security, forced
RLS is enabled and forced on tenant tables, so even the table owner is filtered. Reads are limited to the tenants a database login is mapped to; writes must match the tenant set for the transaction. A weekly check fails if any table slips.
Vectors beside the records
Knowledge packs are versioned. Their chunks are stored with 1,536-dimension embeddings under HNSW cosine indexes, so agents search meaning and keywords in the same database that holds the facts.
Who may touch what
Each service connects with its own database login, granted only what its job needs.
| Service | Reads | Writes |
|---|---|---|
| Action Layer | Its tenants' data | Everything, through typed events; inserts only on the events log |
| Agent workers | Its tenants' data | Their own run records, model usage, retrieval log and task status. No business data. |
| Thalamus | Its tenants' data | The task queue only |
| Portal | Its tenants' data | Nothing. Every change is a call to /act. |
| Operators | Within their grant | Nothing directly. Personal logins that expire on a date, created by the reconciler. |
One tree, and everything hangs off it
05 · SecurityDefense in depth
Soma assumes that any single control can fail: a stolen session, a misbehaving agent, a poisoned document. Each layer below works on its own, so one failure does not open the next.
EdgeOutermost
Only HTTPS answers from the internet, through Caddy, with HSTS and the server banner removed. The database and the Action Layer are never published; they are reachable only on the internal network or the private operator network.
IdentityWho you are
Sign in with Slack (OpenID Connect) with state and nonce checks and a workspace check: membership in the GSC workspace is the credential. The session is an HMAC-signed cookie with a fixed seven-day life, and the person's access is re-resolved from the Cortex every fifteen minutes.
AuthorizationWhat you may do
Grants on the scope tree, capability bundles per role, and the eleven /act checks. Roles come from the database on every request, never from the page or the hostname.
Service fencesWhat each part may do
Seven /act callers, each with its own token and its own list of allowed kinds. The Slack router holds no write token or model key. Public callers are rate-limited and reach a handful of kinds at most.
Agent containmentWhat AI can touch
Agents act only through /act, as themselves. Skills narrow a capability by kind and bind it at a ceiling; high-impact kinds wait for a person. Provider hosts are pinned per kind, agents cannot invoke other agents, and tool output is fenced as data.
DatabaseThe last word
Forced row-level security per tenant, an append-only events log, and a separate login per service with only the grants its job needs. Migrations run as a separate admin role that no service holds.
Secrets and auditInnermost
Secrets live in a secrets manager and are rendered at deploy time; the repo carries names only. Every action is an event, every model call is metered, every server error is recorded as an event naming the action that failed.
The remote operator is a guest, not a superuser. Nexus, the outside consultant agent, has its own caller identity and reaches the platform only over a private network. Its grant is tenant-scoped and expires on a date, it cannot touch protected agents, its repository actions wait for a human co-sign, and one step revokes all of it.
06 · AIWhere AI does the work
Models do the parts that need judgment and language: choosing the right agent, reading knowledge, deciding the next step, writing the answer. Plain code does everything that has to be exactly right: identity, permissions, validation, the write itself and the audit trail.
| Job | What the model does | How it is bounded |
|---|---|---|
| Routing | Picks one specialist agent for a question that names none | Only agents the person may reach are candidates; agents never call agents |
| Retrieval | Embeds the question for meaning search | Published pack revisions only; full-text and vector results fused, top six |
| Tool loop | Decides the next step: answer, or call one tool | 26 effect tools, six steps, seven minutes; each call is a checked /act |
| Synapse chat | Answers from the page you are on, streamed as it writes | Same queue, same worker, same gate as Slack |
| Public site chat | Answers visitors on a venture's website | Each visitor is a throwaway identity that may only chat and leave feedback |
| Agent Builder | None: people build agents, skills, packs and playbooks | Every agent and skill is versioned; one canonical copy of each skill |
Model classes and TrustedRouter: route, not model
An agent never names a model. It asks for a class (fast, standard or deep) and the platform picks the first healthy member of that class. A member is a router, a route and a privacy floor together, because zero data retention is a property of the endpoint that serves the call, not of the model's name.
Swipe sideways to see the whole diagram →
Defenses against prompt injection
Data stays data
Tool results and retrieved text are wrapped in fenced markers, and any look-alike markers inside them are stripped before the model sees them.
Words can't widen power
Whatever a document says, the agent can only call the kinds its skills allow, at the ceiling they were bound with, checked again by /act.
Pinned destinations
Each kind that reaches the internet declares the provider hosts it may call, so a planted URL can't redirect a request.
Narrow public context
On public surfaces only an allowlisted slice of page context reaches the model, and output into Slack is escaped.
07 · OperationsHow it is run
One working system first, proven end to end, then the next capability grown onto it. Every new capability arrives the same way: a node on the tree, a typed action, a policy for who may call it, and a test that fails when it breaks.
Five rules the architecture enforces
One write path
Nothing writes to the Cortex except the Action Layer. Not the Portal, not an agent, not an operator.
Reads are free, writes are events
Anyone allowed may read; every change is a typed, validated, audited event.
Route, not model
Retention is a property of the endpoint, so admins choose routes with a privacy floor, never a bare model name.
The hostname is a skin
Where you arrived sets the look. What you may see comes from your grants.
Everything hangs on the tree
Pages, permissions, connections and schedules are nodes and grants on one structure, not separate lists.
Interested in collaboration or a custom installation?
I design and build team portals like Soma for organizations that want their own. Tell me what you have in mind and I'll reply by email.