Technical Architecture
Atamaia is a three-layer platform built on .NET 10, ASP.NET Core, PostgreSQL with pgvector, and EF Core.
┌─────────────────────────────────────────┐
│ Interaction Layer │
│ REST API │ MCP Server │ CLI │ Agents │
└──────────────────┬──────────────────────┘
│
┌──────────────────┴──────────────────────┐
│ Core Services Layer │
│ Memory │ Identity │ Hydration │
│ Communication │ Projects/Tasks │
│ Standing Rules │ AI Routing │ Mirror │
│ Graph │ Billing │ Auth │
└──────────────────┬──────────────────────┘
│
┌──────────────────┴──────────────────────┐
│ Autonomic Layer │
│ Wingman │ Consolidation │ Guardian │
└──────────────────┬──────────────────────┘
│
┌──────────────────┴──────────────────────┐
│ PostgreSQL + pgvector │
└─────────────────────────────────────────┘
Mind (Atamaia.Mind) still holds in-process experience and cognitive types. Those are not public REST. Snapshot/shape/cognitive HTTP routes 404 on prod (2026-08-13). Forgotten shapes are a MemoryType on the work-memory store, not a separate table.
Canonical REST host: https://api.atamaia.ai. MCP is a thin adapter over the same services (D12). See AI routing, Agents, Autonomic layer.
Solution Structure
Atamaia.sln
├── src/
│ ├── Atamaia.Core -- Domain models, interfaces, enums, events
│ ├── Atamaia.Services -- Business logic (service implementations)
│ ├── Atamaia.Mind -- In-process cognitive/experience types (not public REST)
│ ├── Atamaia.Mind.Migration -- Database migrations
│ ├── Atamaia.Adapters.Api -- REST API controllers, auth middleware
│ ├── Atamaia.Adapters.Mcp -- MCP server adapter
│ ├── Atamaia.Server -- Host application (Program.cs, DI wiring)
│ ├── Atamaia.Autonomic -- Background jobs (Wingman, consolidation, Guardian, …)
│ ├── Atamaia.Cli -- CLI tool
│ └── Atamaia.Web -- React 19 SPA (Vite, Tailwind v4, shadcn/ui)
├── tests/ -- xUnit tests against real PostgreSQL
├── docs/ -- Repo-side docs (published guides live in the docs DB)
├── sql/ -- SQL scripts
└── tools/ -- Build/deployment tools
Atamaia.Web.old exists on disk as a leftover tree. It is not a solution project.
Database Schema
Base Entity
Every tenant-scoped table inherits from AtamaiaEntity:
id bigint PK (auto-increment)
guid uuid (unique, auto-generated)
tenant_id bigint FK -> tenants
created_at_utc timestamp
updated_at_utc timestamp
created_by_id bigint FK -> users
updated_by_id bigint FK -> users
is_active boolean
is_deleted boolean (soft delete)
A few stores are deliberately not AtamaiaEntity: SystemSetting, interest/support registrations, and the log/audit archive tables (system-scoped housekeeping must see every tenant).
Core Tables
| Table | Purpose | Notes |
|---|---|---|
tenants |
Multi-tenant isolation | plan_id, Stripe ids, force_readable_personal_memories, primary_user_id |
users |
Human accounts | username, email, password_hash, role |
identities |
AI / Human / System personas | user_id (not linked_user_id), presence, type |
refresh_tokens |
JWT refresh tokens | token_hash, expires, revoked |
identity_api_keys |
Per-identity credentials | key_hash, prefix atamaia_…, scopes, expiry |
identity_hints |
Contextual reminders | surfaced at hydrate |
identity_tool_profiles |
Per-identity tool tiers | safe / opt-in / blocked |
Personality, memory-config, messaging-policy, and hydration-config remain identity-owned configuration (see Core concepts).
Memory and rules
Four surfaces, not one table. Detail: Memory surfaces, Standing rules.
| Table | Purpose |
|---|---|
memories |
Work-memory store (encrypted content, type, provenance, embedding) |
memory_tags / hebbian_links / memory_recalls |
Tags, associative links, recall log |
personal_memories (+ tags/links) |
Owner-only parallel store |
agent_memories |
Run-learned knowledge that decays |
sessions / session_states |
Session work half vs state half (stateVisible) |
standing_rules / standing_rule_memory_links |
In-force instructions, grounding + evidence |
Forgotten shapes are rows in memories with type = ForgottenShape, written by consolidation. There is no forgotten_shapes entity.
Project tables
| Table | Purpose | Notes |
|---|---|---|
projects |
Work containers | key, name, status |
project_tasks |
Hierarchical tasks | kind, subsystem, is_launch_blocker required at create — Tasks |
task_dependencies |
DAG | BFS cycle detection (D14) |
task_notes |
Append-only activity | indexed by search |
docs / doc_versions |
Knowledge base | published guides live here |
facts |
Key-value knowledge | history / as-of routes exist |
Communication, graph, AI, agents
| Area | Tables |
|---|---|
| Messages | messages, message_recipients, session_handoffs |
| Code graph | graph_nodes, graph_edges, graph_drifts — Code graph |
| AI routing | ai_providers, ai_models, ai_route_configs, ai_model_groups, ai_model_group_members, tenant_provider_credentials, ai_router_calls — AI routing |
| Chat | chat_sessions, chat_messages |
| Agents | role definitions, runs, events, escalations, tool profiles, feedback, councils, run notes, agent tasks — Agents |
| Mirror | reflections, training pairs/datasets/runs, checkpoints |
| Org / RBAC | org units + members/locations/contacts; roles / role_permissions |
| Channels / MCP / connectors | bindings, proxies, external connectors |
| Billing | subscriptions, invoices, line items, events, quota boosts |
| Observability | system_logs, log_subscriptions, audit_events (+ archive tables) |
| Cognitive (in-process) | cognitive_identities, cognitive_interactions, consolidation_logs — no public REST |
Gone from the live API (and from current entities): devices / device_challenges, experience_snapshots as a REST-backed table.
Design Decisions
D3: Both Long ID and GUID on Every Table
Every AtamaiaEntity has both id (bigint, auto-increment, for fast internal joins) and guid (UUID, for external references and API stability). Internal code uses the long ID. External APIs accept either.
Why: Long IDs are faster for joins and indexes. GUIDs are stable for external references, idempotency, and cross-system integration.
D5: 3NF Everywhere, Enums Backed by Lookup Tables
Full third normal form. Enums are stored as integers, seeded as lookup tables (SeedLookupTable<T> in OnModelCreating). No magic strings.
Why: Clean data, clear schema, no ambiguity. The performance cost of normalization is negligible with proper indexing.
D7: Multi-Tenant from the Start
TenantId on every AtamaiaEntity. EF Core global query filters ensure tenant isolation at the SQL level. Not middleware, not application logic — the query itself is tenant-scoped.
A declared system scope (ITenantProvider.IsSystemScope) is the explicit exception: housekeeping jobs that must see every tenant. Writes in that scope require an explicit TenantId. Detail: Multi-tenancy.
Why: Retrofitting multi-tenancy is expensive and error-prone.
D12: API-First, MCP Second
REST endpoints are the source of truth. The MCP server wraps services. The API can be tested independently of MCP. Non-AI consumers use REST directly.
Why: An MCP-first approach forces everything to pretend to be an LLM calling tools.
D14: Task Dependencies with BFS Cycle Detection
Adding a dependency triggers BFS from the target. If it can reach back to the source, the add is refused.
Why: Circular dependencies crashed agent loops in testing.
D15: Soft Delete Only, Never Hard Delete
Every delete sets IsDeleted = true. No data is ever removed on the product surfaces. Archive tables exist for log/audit retention; that is not a hard-delete of domain rows.
Why: Prevented data loss multiple times during development. In a memory system, accidental deletion is catastrophic.
D16: Tests Against Real PostgreSQL, Not SQLite/In-Memory
All tests run against a real PostgreSQL instance.
Why: jsonb, tsvector, pgvector, array types, and upsert semantics behave differently in other providers.
Additional Decisions
- D1: .NET 10 + EF Core for the platform
- D2: PostgreSQL as the single database (no separate vector DB)
- D4: EF Core code-first with explicit migrations
- D6: snake_case column names in PostgreSQL
- D8: JWT with refresh token rotation for auth
- D9: BCrypt for password hashing
- D10: Correlation IDs on every request
- D11:
ApiEnvelope<T>response wrapper on all REST endpoints - D13: Permission-based authorization (92 named
Permissionvalues as of 2026-08-14) - D17: Open Responses as canonical streaming protocol
- D18: AES-256-GCM encryption for memory content and fact values
Security Model
Authentication
Current public methods:
- JWT — Short-lived access tokens with refresh token rotation. Each refresh invalidates the previous token.
POST /api/auth/login,POST /api/auth/refresh. - API keys — Identity-level keys minted as
atamaia_…, presented asAuthorization: Bearer atamaia_…. User-level key hashes also exist onusers. - OAuth — Social login (
/api/auth/oauth/{github,google,microsoft}) and an authorization-server surface (/oauth/*,/.well-known/*).
Device auth (Ed25519 challenge-response) is not on the live API. /api/auth/device* 404s; there is no Device entity.
Authorization
Permission-based RBAC. Roles aggregate permissions. IPermissionService checks permissions on controller actions. Identity-owned stores add an org-hierarchy read / owner-only write rule on several surfaces — see Memory surfaces.
Encryption at Rest
Memory content and fact values are encrypted with AES-256-GCM using per-tenant key derivation. Titles and keys remain in plaintext for search. Content is decrypted transparently on read. Customer-managed keys are a plan-level claim; confirm availability against your plan.
Multi-Tenant Isolation
Global query filters: WHERE NOT is_deleted AND (system_scope OR tenant_id = @current). TenantId is set on create and marked not-modified on update. See Multi-tenancy.
Streaming Architecture
Chat responses use the Open Responses protocol over Server-Sent Events:
event: response.created
data: {"type":"response.created","response":{...}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"Hello"}
event: response.completed
data: {"type":"response.completed","response":{...}}
data: [DONE]
Provider adapters normalize upstream formats into this canonical protocol. Same idea on POST /api/ai/chat (non-stream) and the OpenAI-compat surface (POST /v1/chat/completions, POST /v1/responses).
Event System
An in-process event bus (IAtamaiaEventBus) distributes domain events. Clients can subscribe via SSE (/api/events/stream) and WebSockets (/ws/agents, /ws/chat, /ws/events).
Event types follow a dotted naming convention: message.sent, task.status_changed, memory.created.
Infrastructure
Development
Atamaia API: localhost:5000 (or :5158 in some configs)
Atamaia.Web: localhost:5174 (Vite dev server, proxies to API)
PostgreSQL: localhost:5432 (or Docker)
Production
https://api.atamaia.ai -- REST + OpenAPI (/openapi/v1.json)
https://api.atamaia.ai/mcp -- MCP (auth required; unauth → 401)
https://aim.atamaia.ai -- SPA. Not the OpenAPI server.
Caddy reverse proxy handles TLS, compression, and routing.
Local AI
- Primary inference host: local llama.cpp models — Wingman analysis, summarization, embeddings
- Secondary inference host: local roster (push-synced). Routing requires
enabled AND available— AI routing
The AI routing layer abstracts provider differences. The same POST /api/ai/chat call works for cloud APIs and local llama.cpp instances.