Orbis Schema Map

How the Orbis tables fit together · 2026-04-24

The fleet

Every paying customer is a tenant. Each tenant gets their own database, their own worker, their own WhatsApp number. A shared orbis-admin control plane keeps track of all of them but never holds a tenant's conversation data.

orbis-admin

Fleet control plane · 1 shared DB

orbis-dreamlink

Agency vertical
Customer #1 · live 2026-04-24 · 169 contacts, 3,168 conversations

orbis-nova

Travel-concierge
Queued for Phase 2B

orbis-gaku

Agency vertical
First external paying customer · onboarding

orbis-{future}

Any vertical
Every future customer — same shape

Every tenant DB has the same core schema plus one vertical overlay. Zero cross-tenant data. Zero shared tables between customers.


Inside one tenant's database

Three stacked layers. The bottom is universal, the middle is industry-specific, the top is bespoke. A tenant can opt out of the upper two — a plain Orbis with zero verticals works fine.

Custom layer
Bespoke modules for enterprise customers whose workflows don't fit any template. Priced as professional services. Empty for most tenants.
(tenant-specific)
Vertical layer
Industry overlay — agency, travel-concierge, family-office, fund-ops. Each vertical ships schema + tools + prompts + config as a versioned artifact. One vertical per tenant at a time.
deals deal_activities pm_projects pm_tasks pm_staff pm_deliverables pm_stakeholders pm_activity pm_weekly_submissions talent_profiles talent_prospects talent_positioning_history entity_memberships
Shown: agency vertical · travel-concierge would show trips / bookings / celebrations / households instead.
Core layer
Universal — every Orbis tenant has this schema. Maintained once by Dreamlink, shipped to every customer. Distilled 2026-04-24 from 82 → 62 tables by dropping dead / redundant / patch-stacked schemas and consolidating three dedup tables into one and two state tables into orbis_state.
Identity & access
contacts contact_auth contact_jids contact_preferences access_permissions entity_flags relationship_health
Conversations & channels
conversations group_chats group_messages slack_messages slack_channels slack_channel_access discord_messages discord_channels discord_guild_config email_messages
Containers / multiplayer isolation
containers container_shares group_unknown_contacts clearance_audit_log disclosure_audit_log
Memory & knowledge
memory knowledge observations embeddings_log meetings interactions
Relationship intelligence (EQ)
energy_signals person_models corrections
Self-improvement (mirror loops)
mirror_loops mirror_sessions mirror_saboteur_patterns
Trust layer (every tool call audited)
executions execution_steps execution_claims tool_invocations policy_decisions resolution_results resolution_cache fabrication_log watchdog_scores watchdog_daily
Leon operational state
leon_errors leon_decisions behavioral_rules orbis_state
Scheduling & queues
scheduled_actions action_queue message_queue processing_locks scheduling_sessions
Tool output & infra
artifacts voice_memos completions counters settings dedup router_log receipt_audit_incidents preference_change_log
Generic tables — new 2026-04-24 (the only legal home for future log/state/flag writes)
events workflow_state orbis_kv
Any new Leon feature logs events → events (with kind), tracks in-flight state → workflow_state, stores flags/caches/dedup → orbis_kv. No new bespoke tables. Enforced by review + lint hook. See docs/orbis-schema-discipline.md.

The identity thread

One table — contacts — is the canonical person record. Everywhere else in the schema where a person appears, the table holds a contact_id pointing back at contacts.id. No parallel users table. No WhatsApp LID stored as identity anywhere.

conversations
→ contact_id
Every message (WhatsApp / Slack / Discord / email) logged back to the sender, unless the sender is an unresolved orphan (NULL + sender_name preserved).
contacts
id · name · phone · email · whatsapp_jid · slack_id · discord_id · role · tier · energy_state
One row per person. Absorbed the old users table. Phone is the canonical identifier for routing. WhatsApp LIDs are never stored.
memory
→ contact_id
Structured facts about a person — preferences, last interaction, operator-level metadata.
interactions
→ contact_id
Per-touch log — channel, direction, summary. Feeds relationship_health scoring.
scheduled_actions
→ contact_id
Reminders and recurring actions for / about a contact.
group_messages
→ sender_contact_id
Group-chat messages resolved to the sender contact when identity can be determined from membership.
deals (vertical)
→ contact_id
Agency vertical — every deal is tied to the primary contact on the other side.
talent_profiles (vertical)
→ contact_id
Agency vertical — every talent on the roster is a contact first, talent second. One talent profile per contact.
household_members (travel vertical)
→ contact_id
Travel vertical — every household member is a contact. Households are groupings over real people, not independent identities.
containers
→ member_contact_ids (JSON)
Group chats reference their members as contact ids. When someone leaves a group, their contact stays intact — they just stop appearing in the membership array.
group_unknown_contacts
→ resolved_contact_id
Unresolved senders get pointed at a real contact once classified via the A-F/N menu. Until then, Orbis keeps them outside the trusted context.

If you ever touch a person anywhere in the system — sending them a message, adding them to a deal, logging a meeting, booking a trip for them — it lands in contacts first. Everything else references.


Core tables in detail

A closer look at what sits in each box, with the columns that matter for understanding the FK relationships.

Identity & access

contacts
id (PK)
name · phone · email
whatsapp_jid · slack_id
contact_type · role
relationship_tier · tier_score
energy_state
merged_into → contacts.id
access_permissions
contact_type · contact_id
can_assign_tasks
can_query_knowledge
can_query_contacts
can_query_sensitive
can_modify_contacts
entity_flags
entity_id
flag_type · severity
description
resolved_at
relationship_health
entity_id (PK)
total_interactions
health_score
last_contact_days

Conversations & channels

conversations
id (PK)
contact_id → contacts
sender_name (when orphan)
role · message
channel · product_context
timestamp
group_chats
group_jid (PK)
group_name
stage
group_messages
id (PK)
group_jid
sender_contact_id → contacts
message · orbis_response
slack_messages
id (PK)
channel_id
sender_contact_id → contacts
message · thread_ts

Memory, knowledge, actions

memory
id (PK)
contact_id → contacts
type · key · value
visibility · sensitive
expires_at
knowledge
id (PK)
scope · section
content
meetings
id (PK)
calendar_event_id
title · attendees
start_time · end_time
summary
scheduled_actions
id (PK)
contact_id → contacts
action_type
scheduled_for
title · body · recurrence
interactions
id (PK)
contact_id → contacts
interaction_type
channel · direction
summary
behavioral_rules
id (PK)
rule
status

Containers & isolation (multiplayer protocol)

Every group chat is a walled garden. Knowledge shared inside a container stays in the container unless an explicit container_share audit trail is written. Orbis never silently crosses a boundary.

containers
id (PK)
platform · name
container_type
member_contact_ids (JSON)
sensitivity
last_active_at
container_shares
id (PK)
knowledge_id
from_container_id → containers
to_container_id → containers
requested_by_contact_id → contacts
approved_by_contact_id → contacts
status · resolved_at
group_unknown_contacts
id (PK)
group_jid · sender_jid
sender_phone · push_name
resolved_name
resolved_contact_id → contacts
status · nudge_count
access_permissions
contact_type · contact_id
can_query_contacts
can_query_sensitive
can_send_as_orbis
(see also: identity section)

Trust layer (every tool call is audited)

executions
id (PK)
tenant_id · workflow_type
user_input
normalized_intent
final_output · status
execution_steps
id (PK)
execution_id
step_index · step_type
input_json · output_json
status
execution_claims
id (PK)
execution_id · step_id
claim_key
source_type · source_ref
verification_status
tool_invocations
id (PK)
execution_id · step_id
tool_name · tool_version
request_json · response_json
latency_ms · success
policy_decisions
id (PK)
execution_id · step_id
sensitivity_category
audience_context
decision · reason_code
resolution_results
id (PK)
execution_id · step_id
entity_type · raw_query
matched_id · matched_name
confidence · status
fabrication_log
id (PK)
fabrication_type
leon_claim
evidence
severity · detected_at
watchdog_scores
watchdog_daily
per-response trust scoring
rolled up into daily trust score
for the admin dashboard

Agency vertical (Dreamlink's overlay)

These tables only exist in tenants running the agency vertical. They sit on top of the core and reference contacts for every person.

Deal pipeline

deals
id (PK)
contact_id → contacts
entity · title
stage · value
expected_close_date
deal_activities
id (PK)
deal_id → deals
activity_type
old_value · new_value
entity_memberships
contact_id → contacts
entity
role · title

Project management

pm_staff
id (PK)
name · email · phone
role · active
pm_projects
id (PK)
name · entity · status
owner_staff_id → pm_staff
pipeline_stage · progress
pm_tasks
id (PK)
project_id → pm_projects
title · assignee
status · priority · due_date
pm_deliverables
id (PK)
project_id → pm_projects
title · quantity
completed_count · due_date
pm_stakeholders
id (PK)
project_id → pm_projects
orbis_contact_id → contacts
contact_role · territory
pm_activity
id (PK)
project_id → pm_projects
task_id · staff_name
action · source
pm_weekly_submissions
id (PK)
staff_id · staff_name
week · content

Talent roster

talent_profiles
id (PK)
contact_id → contacts (UNIQUE)
category_lock
positioning_statement
current_stage · current_cycle
talent_prospects
id (PK)
contact_id → contacts
scout_source
scorecard_*
decision
talent_positioning_history
id (PK)
talent_id → talent_profiles
field_name
previous_value · new_value
rationale

Current state of orbis-dreamlink

78tables
169contacts
3,168conversations
94%identity-resolved
Phase 2A — identity layer migrated

What's next

Phase 2B — migrate knowledge, deals, PM, meetings, trust-layer data into orbis-dreamlink.
Phase 3 — flip the nine workers that currently read nova-leon to read orbis-dreamlink instead, one at a time, with dual-write windows.
Phase 4 — extract dreamlink-ops (agency PM/staff for Dashboard) and nova-db (travel product) out of orbis-dreamlink.
Phase 5 — drop nova-leon and provision the first external tenant (Gaku) against the proven architecture.