all insights

Data graphs in Data 360: the low-latency layer that grounds an agent on a live record

An agent mid-conversation can't run a 12-table join and make the customer wait. Data graphs are Data 360's answer — a pre-joined, denormalized view of a customer served in milliseconds. Here's how they work, the real-time vs. batch trade-off, the 200 KB discipline nobody explains, and where they quietly cost you.

Data graphs in Data 360: the low-latency layer that grounds an agent on a live record — article illustration

A customer opens a chat and asks, “why was I charged twice?” For the agent to answer well, it needs the person’s account, their last three orders, the two open cases, their loyalty tier, and the payment that looks duplicated — all of it, joined, in front of the model before it writes a word. In a normalized data model that’s a join across a dozen objects. Run that live, mid-turn, against tens of millions of records, and the customer is staring at a typing indicator for three seconds while the query planner sweats. Do it on every turn of every conversation and you’ve built a latency problem that no prompt engineering fixes.

This is the grounding problem nobody demos, because the demo has one clean record. We’ve written about the other two grounding paths already — grounding agents on a semantic layer so they stop inventing your metrics, and Intelligent Context so they retrieve the right passage from your documents. Data graphs are the third path, and the one people reach for last: how an agent reads the live, structured record of the customer in front of it, fast enough that the read is invisible. This post is what a data graph actually is, the real-time-versus-batch decision that decides both freshness and cost, the size limit that quietly governs your design, and where the whole thing stops.

Why you can’t just query the DMOs live

Data 360 stores unified data in a normalized model — data model objects (DMOs) with relationships between them, the same way a relational database does. That normalization is correct for storage and terrible for real-time reads. Teams routinely map data across 10 to 25 or more related objects; a serious customer profile touches account, contacts, orders, order items, cases, subscriptions, loyalty, and consent. Reconstructing “everything about this person” means joining all of those on demand, and joins across many objects at scale are exactly the operation that blows past a conversational latency budget.

An agent that makes the customer wait while it joins twelve tables isn’t grounded — it’s slow. Real-time grounding is a data-modeling problem before it’s an AI problem, and the model you need is the opposite of the one you store in.

A data graph flips the trade. Instead of joining at read time, Data 360 does the join ahead of time and materializes the result as a single, pre-joined, denormalized JSON document — one per primary record. The agent (or a Personalization rule, or a Marketing Cloud journey) reads that one document instead of running the join. Salesforce’s own engineering write-ups describe data graphs serving sub-second reads over hundreds of millions of records precisely because the expensive work already happened. You trade storage and refresh cost for read latency, and in a conversational context that’s the right trade every time.

A data graph is built inside a data space and has exactly one primary DMO — the root of the tree, the thing each document is “about.” For a service agent that’s usually the unified individual (the person). Everything else hangs off it as related objects, which fall into the Data 360 categories you already know: Profile, Engagement, and Other. Profile is the golden-record stuff (the person, their contact points), Engagement is behavioral (web events, orders, interactions), Other is everything else (products, accounts, reference data).

The relationship you draw between the primary and each related object carries a cardinality — one-to-one or many-to-one — and this is the first decision that bites, because cardinality is immutable once the graph is created. A person has one loyalty tier (one-to-one) but many orders (many-to-one). Get it wrong and you don’t edit it; you rebuild the graph. Model the tree on paper before you click, and model it the way the agent will read it, not the way your source systems happen to store it.

The result, conceptually, is a document shaped like the customer:

{
  "ssot__Id__c": "indiv-0f1a...",
  "ssot__FirstName__c": "Dana",
  "loyalty_tier__c": "Gold",
  "Orders__dlm": [
    { "order_no__c": "SO-40021", "amount__c": 129.00, "status__c": "Shipped" },
    { "order_no__c": "SO-40088", "amount__c": 129.00, "status__c": "Processing" }
  ],
  "Cases__dlm": [
    { "case_no__c": "00098231", "subject__c": "Duplicate charge", "status__c": "Open" }
  ]
}

One read returns the whole shape. No join, no wait. The agent’s grounding step walks that JSON instead of querying your data model, and the retrieval that used to be a latency risk becomes a key lookup.

Real-time vs. batch: the decision that sets freshness and cost

Data 360 gives you two flavors of graph, and choosing between them is the single most consequential thing you’ll do here.

A batch data graph is materialized on a schedule. The default refresh is daily, and you can tighten or loosen it to hourly, every four hours, weekly, or monthly. Newer incremental refresh processes only changed records instead of rebuilding the whole set, which cuts the cost of a frequent schedule. Batch is right for data that doesn’t change between conversations — loyalty tier, account attributes, historical orders. The customer’s Gold status is not going to flip mid-chat.

A real-time data graph updates with sub-second latency from streaming engagement data (web and mobile events). It’s what you want when the agent must react to something that just happened — the item added to the cart thirty seconds ago, the page the customer is on right now. But real-time carries a hard constraint that shapes everything: a documented size cap of roughly 200 KB per graph record. That is not a lot of JSON. It is enough for a lean, current-state view and nothing more.

The mistake is treating real-time as “batch but fresher” and stuffing a customer’s entire history into it. You can’t — the 200 KB ceiling won’t let you, and if you get close, refresh frequency degrades. The discipline is to split the model: a batch graph for the durable, wide profile, and a lean real-time graph for the handful of just-happened signals, and let the agent read both. Real-time graphs also cap at around 100 million records; batch graphs scale far higher. Deeply nested relationships hurt both — too many levels of nesting is a known cause of query timeouts and slow refreshes, so keep the tree shallow and wide, not deep.

How an agent actually reads it

The agent never touches the graph as a raw query. Grounding flows through a retriever — the same retriever abstraction that fronts vector search for unstructured data, but here pointed at a data graph — and the retriever is a resource inside a prompt template or wired to an agent action. You configure which graph, which record (keyed off the conversation’s context — a customer id, a case’s contact), and which fields come back. The prompt template then grounds the model on that returned JSON instead of on the model’s training.

The two things that matter in that configuration are the key and the scope. The key is how the retriever finds the right document — almost always an id carried in the session context, never something the user typed. The scope is which fields and which related objects the agent is allowed to see. A retriever that returns the whole graph when the agent needed three fields is both a cost problem and a data-minimization problem:

retriever:
  name: customer_profile_dg
  source: data_graph
  data_graph: Customer_360_Batch
  key:
    field: ssot__Id__c
    value: "{{context.individualId}}"   # from session, not user input
  return:
    - ssot__FirstName__c
    - loyalty_tier__c
    - Orders__dlm[*].order_no__c
    - Orders__dlm[*].status__c
    - Cases__dlm[*]
  max_related: 20

That individualId coming from session context, not from anything the customer typed, is the boundary that keeps one person’s agent from reading another person’s record. It’s the structured-grounding version of the tenant filter we harp on in the least-privilege guide for agents: scope the read server-side, to the record the conversation is already about.

Where the credits go

Data graphs are not free plumbing, and the cost has two parts. Building and refreshing the graph consumes Data 360 credits — every scheduled materialization does work, and a graph set to hourly across a large population does that work often. Querying the graph consumes credits too, metered against usage. An agent that fires a graph retrieval on every conversational turn multiplies the query cost by your conversation volume, and a real-time graph refreshing continuously is a standing meter, not a periodic one.

The optimization levers are the obvious ones once you see the meter: don’t refresh faster than the data changes (a daily-changing profile does not need an hourly graph), keep real-time graphs to the few fields that genuinely need sub-second freshness, and don’t return fields the agent won’t use. This is exactly the class of setting our Data 360 credit optimization playbook exists to catch — the streaming that didn’t need to stream, the refresh nobody asked for — and data graphs are one of the easiest places to accidentally turn a knob to “expensive.” If you’re still sizing your first Data 360 commitment, the mechanics of how credits accrue are in our pricing and credits guide.

Where data graphs stop

The honest section, because a materialized view sold as a silver bullet is how teams get a surprise three months in.

  • A graph inherits your identity resolution. The primary object is a unified individual, which means every over-merge upstream lands in the graph as two people fused into one document — and the agent now grounds on a record that mixes two customers. The graph faithfully serves whatever identity resolution handed it. Tune match rules conservatively first; the graph amplifies both the good unification and the bad.
  • Batch means stale between refreshes. A daily graph is up to a day old. For durable attributes that’s fine; for anything that changed since the last refresh, the agent is confidently reading yesterday. If an answer must reflect a state change from minutes ago, that field belongs in the real-time graph or in a live callout, not in a batch document.
  • Cardinality and structure are set at creation. One-to-one vs. many-to-one can’t be changed later, and reshaping the tree means rebuilding. This is a design-once surface. The cost of getting it wrong is a migration, so spend the time on the model.
  • It’s structured record context, not metrics and not documents. A data graph answers “what is true about this customer right now.” It does not compute “what was net revenue in the West” — that’s the semantic layer — and it does not retrieve “what does this contract say” — that’s Intelligent Context. A serious agent uses all three, each for the question shape it fits, and doesn’t try to answer a metric question by reading a profile document.

What to actually do

If you’re grounding an agent on live CRM and customer data today and it feels slow, or you’re about to point a retriever straight at your DMOs and join at read time, a data graph is the fix — but design it deliberately. Model the tree the way the agent reads, root it on your unified individual, and set cardinalities carefully because you’re stuck with them. Split durable, wide profile data into a batch graph and the few just-happened signals into a lean real-time graph inside the 200 KB budget. Key every retriever off session context, never user input, and return only the fields the agent uses. Refresh no faster than the data actually changes, and watch the query meter once the graph is live. Do that and the most expensive read in your architecture — everything about this customer, joined — becomes a millisecond lookup the customer never notices.

That invisibility is the whole point. The difference between an agent that feels like it knows the customer and one that stalls and then guesses is, more often than not, whether the record reached the model fast and whole. Data graphs are how you make that read disappear. Whether you need the paid Data 360 implementation behind them at all is its own question — we mapped it in does Agentforce need Data Cloud — but if you’re grounding on real customer data at conversational speed, this is the layer doing the quiet work.

Understanding the basics

What is a data graph in Salesforce Data 360?

A data graph is a pre-joined, denormalized JSON view of a record and its related data, materialized ahead of time and stored in a low-latency store so it can be read in milliseconds. Instead of joining many normalized data model objects at read time, an agent, personalization rule, or journey reads one already-assembled document. Each graph has a single primary DMO as its root, with related objects from the Profile, Engagement, and Other categories hanging off it. Its purpose is to ground AI and real-time experiences on a complete customer record without the latency of a live multi-object join.

What’s the difference between a real-time and a batch data graph?

Freshness and constraints. A batch data graph refreshes on a schedule — daily by default, adjustable to hourly, every four hours, weekly, or monthly — and is right for durable data that doesn’t change mid-conversation. A real-time data graph updates with sub-second latency from streaming engagement data but is capped at roughly 200 KB per record, so it must stay lean. The common pattern is to run both: a wide batch graph for the durable profile and a small real-time graph for just-happened signals, with the agent reading each.

Do data graphs consume Data 360 credits?

Yes, on two meters. Building and refreshing a graph consumes credits every time it materializes, so a frequently refreshed graph over a large population costs more; querying the graph consumes credits too, metered against usage, which multiplies against conversation volume when an agent retrieves on every turn. The levers are to refresh no faster than the data changes, keep real-time graphs to the few fields that need sub-second freshness, and return only the fields the agent uses — the same discipline in our credit optimization playbook.


Designing the data layer under an agent and not sure whether a slow, wrong, or expensive answer is your graph model, your identity resolution, or your refresh schedule? Talk to us — modeling the grounding layer so agents read the right record fast is exactly the work we do.

Keep reading

All insights