Skip to main content

Two-layer data model

Halo stores everything you know about your users in two related layers:
LayerWhat it isLives inSet via
TraitsSimple key-value identity attributesend_users.custom_fields and companies.custom_fieldsidentify() / identifyCompany() or REST
ContextRich, structured state data for the AIend_users.context and companies.contextsetContext() or chat request body
Traits describe who the user is (name, email, role, plan). Context describes what’s happening for them right now (active errors, integration status, usage metrics). Both are merged into the AI’s system prompt every time the user sends a message.

Where you see them in the dashboard

  • Contacts (/dashboard/users) — every identified user with profile, traits, custom fields, conversation history, and activity feed.
  • Companies (/dashboard/companies) — every identified company with profile, plan, integrations, health score, renewal info, and the linked users.
  • Settings > Data — the schema discovery view: every trait and context key Halo has ever seen, with optional descriptions you can set so the AI understands what each field means.

Data flow end-to-end

1

Your app sends data

From the browser via the Web Widget, from your backend via REST, or from connected integrations like HubSpot.
2

Data is stored

Traits land in dedicated columns (name, email, plan) and the JSON custom_fields blob. Context lands in the JSON context blob.
3

A chat message triggers a merge

When the user sends a message, the chat API loads stored data and merges it with any per-request context the SDK sent.
4

The AI sees structured context

The engine renders all of it into structured, human-readable text in the system prompt — [User Profile], [Company: Name], [Connected Integrations] (integration), etc.
5

The AI responds with full awareness

Knowledge search, tool calls, escalation — all happen with the user’s full state in scope.

Merge strategy

When the AI sees the user’s data on a chat call, it’s merged from up to four places in this order (later wins):
  1. Stored user traits (end_users.custom_fields) — base layer
  2. Per-request custom_fields — overrides matching keys
  3. Stored user context (end_users.context) — base context
  4. Per-request context — overrides matching context keys
Same for the company:
  1. Stored company traits → 2. Request company traits (rare) → 3. Stored company context → 4. Request company context
The result: your app can send fresh, real-time context that takes priority, while persistent data fills in the gaps.

What the AI sees

For a fully populated user, the AI’s system prompt includes something like:
[User Profile]
Name: Jane Doe
Email: [email protected]
Role: admin
Plan: pro

[Company: Acme Corp]
Plan: enterprise
Industry: SaaS
Space ID: space_789

[Active Tracking Events] (list)
- "Purchase Event" (event_name: purchase) -- active
- "Lead Submitted" (event_name: lead) -- paused

[Connected Integrations] (integration)
- Facebook Ads: connected, active
- Google Ads: connected, ERROR -- Token expired
- HubSpot: not connected

[Current Issues] (error)
- Tracking pixel: Pixel not firing on checkout page
The AI references any of this when answering. That’s why “why isn’t my Google Ads tracking?” returns “Your OAuth token expired” instead of “Try reconnecting and let me know if it works.” When you call identifyCompany(), the current user is automatically linked to that company (foreign key end_users.company_id). This means:
  • The AI sees company traits and context alongside user traits on every message.
  • You only need to call identifyCompany() once per user — the link persists.
  • Multiple users at the same company share company-scoped data.
Halo also derives some company-scoped fields automatically from synced integrations:
  • Plan, MRR, lifetime value from Stripe
  • Renewal date, contract terms from Stripe and PandaDoc
  • Health score from your health scoring config
  • team_size computed from the linked end users

Discovered attributes

Halo records every trait and context key you ever send. Settings > Data has tabs for User traits, Company traits, and Context that show:
  • The key name
  • The data type
  • A sample value
  • An optional description you can write (used by the AI to understand what the field means)
Setting good descriptions helps the AI use your custom fields correctly. For example, marking acquisition_channel with the description “How the user found us — paid, organic, referral, or partner” tells the AI to interpret values in that exact set.

Where to go next

User Traits

Recommended and custom user attributes.

Company Traits

Recommended and custom company attributes.

Context Entries

Structured context types reference.

Examples

Real-world integration patterns.

Health Scores

How customer health is computed and used.

Renewals

Track contract terms and renewal dates.