Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Salesforce Life Sciences Cloud architecture diagram separating HCP, patient, clinical and commercial data
Integration

Salesforce Life Sciences Cloud Architecture: HCP vs Patient

Merging HCP and patient records into one Account and Contact hierarchy is cheap on day one and expensive at the first consent audit. Here is the data classification, the integration decision table and the Agentforce guardrail code I would put in place instead.

The short answer

Keep HCP and patient records in separate hierarchies: they carry different consent regimes and retention rules, and merging them makes consent provenance unprovable at audit. Classify every dataset as master, reference, engagement or clinical, let that class pick the integration pattern, and enforce Agentforce guardrails in user-mode Apex.

Key takeaways Model HCP and patient as separate hierarchies, and affiliation as its own object with effective dates, before the first data load. Assign every dataset to master, reference, transactional/engagement or clinical, then choose storage per dataset: store, link, federate, synchronise or cache with a TTL. Pick integration patterns by asking whether a user is waiting for the answer. Use events for notification and APIs when the outcome has to be confirmed. Enforce consent server-side at the moment of use, because an offline save can bypass the validation rule you were relying on. Put Agentforce guardrails in the Apex behind each action: with sharing, WITH USER_MODE, a confirmation token before anything irreversible, and topic-scoped grounding.

The modelling session always goes the same way. Someone points out that a prescriber and a patient are both people with a name, an address and a set of contact preferences, and proposes one Account/Contact hierarchy with record types to tell them apart. It is cheap on day one and expensive at the first consent audit, when you have to demonstrate that an opt-in captured from a physician for professional detailing could never have authorised a message to a patient. Life Sciences Cloud is an industry layer on top of the platform (extra data models and processes for pharma, biotech and MedTech), so every shortcut taken in that first model stays in the org you are extending.

HCP and patient belong in separate hierarchies

The argument for one hierarchy is that both records describe humans. The argument against is that the platform will be asked, repeatedly, to prove which rules applied to which record. HCP and patient data carry distinct consent regimes and distinct retention requirements. Once they share a hierarchy, every sharing rule, retention job and consent check has to re-derive that distinction at runtime from a record type, and each derivation is a place to get it wrong.

Four related shortcuts show up in the same design sessions.

  • Affiliation modelled as a lookup field. A prescriber's relationship to a hospital has a start date, an end date, and often two or three concurrent instances. A lookup holds one of those and remembers nothing. Model affiliation as its own object with effective dates before the first data load.
  • Territory alignment used as security. Alignment answers who is responsible for an account. It does not answer who may read the record. Keep them as two mechanisms even while they happen to produce the same result.
  • Clinical and commercial fields on one object. Different user bases, different compliance contexts. Once a study co-ordinator and a sales rep open the same page layout, field-level security is the only thing standing between them and a finding.
  • Salesforce left as an unnamed secondary master. If the MDM owns HCP identity, write that down and make the org's path to those fields read-only. "Both systems can update it" is a data-quality incident with a future date on it.

I have seen a merged hierarchy unwound eighteen months into a rollout. The migration itself was survivable. Re-collecting consent from patients whose provenance could no longer be evidenced was not, and the programme lost a quarter.

Classify the data before you choose a home for it

Every dataset belongs to one of four classes, and the class, rather than convenience, decides where it lives and who owns it.

Class Examples Typical owner Architectural implication
Master HCP identity, HCO identity External MDM, single golden record Define matching rules, conflict handling, refresh frequency and country-specific identifiers up front
Reference Specialty codes, territory hierarchies External, versioned, slow-changing Version the set; never let users edit local copies
Transactional / engagement Visits, interactions, consent captures Salesforce High volume; needs a stated retention and archive schedule from day one
Clinical Study enrolment, site status Orchestrated in Salesforce, mastered externally Access model separate from commercial; the write-back path must be explicit

Storage strategy then follows per dataset rather than once for the org: store in Salesforce where users edit the data or platform automation drives off it; link out for large sensitive datasets; federate through Data Cloud where the org reasons over data it never edits; synchronise where master data must be queryable and available offline; cache with a TTL for expensive values with a short useful life. A 360 view says what users must be able to see. It says nothing about where the bytes live, and orgs that miss that distinction end up copying every external dataset they touch.

The integration matrix, as a decision table

Pattern selection follows from the data class plus one question: is a human waiting for the answer?

Requirement Pattern Why
HCP identity from MDM Batch or CDC via MuleSoft, or Data Cloud federation The MDM stays system of record; near-real-time buys nothing
Clinical trial status Scheduled batch, or Platform Events Changes are milestone-based, not continuous
EHR/EMR patient data Synchronous REST at the point of use PHI sensitivity demands the narrowest possible scope
Sample and inventory to ERP Asynchronous with guaranteed delivery Financial compliance needs durability, not speed
Market and prescribing feeds Bulk API into Data Cloud High volume, analytical use, no user-edit path
Regulatory and adverse-event submission Synchronous with confirmed receipt The regulatory timestamp is part of the record

Two rules keep the rest of the table honest. Use events for notification and APIs for request/response; when the outcome has to be confirmed, an event is the wrong tool. Federate when the org reasons over data it does not edit, and replicate when offline access or heavy local querying is required. Beyond that, route analytical feeds to Data Cloud instead of custom objects, push large loads through Queueable Apex or Platform Events, and size integration volume against API limits deliberately rather than discovering them in UAT.

Shield Platform Encryption covers "Data at rest, including in backups and some derived indexes". Consent management answers a narrower and far more frequently asked question: "Whether a given channel/purpose is permitted for a given HCP or patient". Encryption satisfies a control. Consent decides whether an action may happen at all, and one will never stand in for the other.

The failure mode worth designing for is offline, because server-side processing happens at a different moment from the user's action. A visit can be saved on a device even though, online, a validation rule demanding a linked consent record would have rejected it: the consent record was itself created offline and has not yet synced. Platform Events queued on a device can fire hours later on reconnection, and anything downstream that assumed near-real-time delivery breaks at that point. Last-write-wins, the default sync behaviour, is the wrong rule for consent and other compliance-sensitive fields.

What I would do: treat the validation rule as a UI hint and enforce consent server-side at the moment of use, when the message is queued rather than when the visit is saved. Constrain the local device footprint deliberately, and treat device encryption and remote wipe as architecture decisions with named owners. GxP (GCP, GMP, GVP), GDPR, HIPAA and 21 CFR Part 11 obligations also extend past technical controls into validation documentation, change control and training records, and they vary by organisation, country and process, which is why country rules and identifiers belong in configuration and not in code.

Agentforce guardrails live in the Apex behind the action

The guardrail everyone quotes is that an agent's actions inherit the same field-level security and sharing rules as a human user. Treat that as a design goal you have to implement. Every agent is associated with a "running user," an identity that determines what the agent can access and perform; new agents have no permissions until you grant them; and "Actions inherit permissions from referenced Apex, Flow, or Prompt Templates". Enforcement lands in the code behind the action.

Summer '26 (API 67.0) makes that cheap. SOQL, SOSL, DML and Database methods default to user mode, a class compiled with no sharing keyword now behaves as with sharing, and WITH SECURITY_ENFORCED no longer compiles. Its replacement, WITH USER_MODE, handles polymorphic fields, applies to all clauses including WHERE, and reports every FLS violation instead of only the first. The architectural work has flipped from remembering to opt in to justifying any opt-out.

public with sharing class HcpEngagementDigest {

    public class DigestRequest {
        @InvocableVariable(label='HCP Account Id' required=true)
        public Id hcpAccountId;

        @InvocableVariable(label='Confirmation token issued by the UI')
        public String followUpConfirmation;
    }

    public class DigestResult {
        @InvocableVariable public String summary;
        @InvocableVariable public Boolean needsHumanConfirmation;
    }

    @InvocableMethod(
        label='Summarise HCP commercial engagement'
        description='Last 90 days of commercial interactions visible to the running user.')
    public static List<DigestResult> summarise(List<DigestRequest> requests) {
        List<DigestResult> results = new List<DigestResult>();
        for (DigestRequest req : requests) {
            results.add(summariseOne(req));
        }
        return results;
    }

    private static DigestResult summariseOne(DigestRequest req) {
        // User mode is the default at API 67.0. Stating it keeps the
        // guarantee if this class is ever recompiled at an older version.
        List<Engagement_Event__c> events = [
            SELECT Id, Channel__c, Occurred_On__c, Discussion_Topic__c
            FROM Engagement_Event__c
            WHERE HCP_Account__c = :req.hcpAccountId
              AND Data_Domain__c = 'Commercial'
              AND Occurred_On__c = LAST_N_DAYS:90
            WITH USER_MODE
            ORDER BY Occurred_On__c DESC
            LIMIT 50
        ];

        DigestResult result = new DigestResult();
        result.summary = renderSummary(events);
        result.needsHumanConfirmation = false;

        if (String.isBlank(req.followUpConfirmation)) {
            // Suggest only. Nothing irreversible happens on this path.
            AgentActionLogger.record('HcpEngagementDigest', req.hcpAccountId, 'SUMMARY_RETURNED');
            return result;
        }

        if (!ConsentGate.channelPermitted(req.hcpAccountId, 'Email')) {
            result.summary += ' Follow-up not queued: no current email consent on file.';
            result.needsHumanConfirmation = true;
            AgentActionLogger.record('HcpEngagementDigest', req.hcpAccountId, 'BLOCKED_NO_CONSENT');
            return result;
        }

        Follow_Up_Request__c queued = new Follow_Up_Request__c(
            HCP_Account__c  = req.hcpAccountId,
            Channel__c      = 'Email',
            Confirmation__c = req.followUpConfirmation
        );
        Database.insert(queued, AccessLevel.USER_MODE);
        AgentActionLogger.record('HcpEngagementDigest', req.hcpAccountId, 'FOLLOW_UP_QUEUED');
        return result;
    }

    private static String renderSummary(List<Engagement_Event__c> events) {
        if (events.isEmpty()) {
            return 'No commercial interactions in the last 90 days.';
        }
        return events.size() + ' interactions, most recent on '
            + events[0].Occurred_On__c.format() + ' via ' + events[0].Channel__c + '.';
    }
}

Two things there are deliberate. The irreversible step is gated on a token the agent cannot mint for itself, which is suggest-versus-act made concrete. And logging is the one place I open the escape hatch: the running user usually has no create access on an audit object, so one small class opts out explicitly, writes in system mode, and exposes no read path at all.

public without sharing class AgentActionLogger {

    // Deliberate opt-out. Write-only, no query methods, no other callers.
    public static void record(String actionName, Id subjectId, String outcome) {
        Agent_Action_Log__c entry = new Agent_Action_Log__c(
            Action_Name__c    = actionName,
            Subject_Record__c = subjectId,
            Run_As_User__c    = UserInfo.getUserId(),
            Outcome__c        = outcome,
            Logged_At__c      = System.now()
        );
        Database.insert(entry, AccessLevel.SYSTEM_MODE);
    }
}

Grounding needs the same discipline. Grounding an agent on everything available "because it's convenient means every topic can potentially surface every dataset", so scope sources per topic and a patient-support agent then has no path to prescribing data at all. Treat external content as untrusted data and never as instructions. "Log everything an agent does, at the same fidelity as a human user's actions": which action ran, what data it touched, what it returned. Then test summarisation, because a summary must not aggregate across an access boundary the user could not have crossed on their own.

What to watch for

  • Unwinding a merged hierarchy is a migration you can plan and cost. Re-collecting consent you can no longer evidence runs on the patient's timetable.
  • A class recompiled below API 67.0 returns to the old defaults with no warning. Pin CI to the current version and fail the build on any WITH SECURITY_ENFORCED.
  • An integration user holding System Administrator defeats every user-mode guarantee sitting above it.
  • Point-to-point integrations multiply. The second one is when to introduce middleware; by the sixth you are paying to retrofit it.
  • A design scoped to a single country becomes a rewrite when the second country arrives. Identifiers, consent rules and retention periods are parameters.
  • Over-customising the industry objects costs you the upgrade path you bought the industry layer for.

Originally reported by salesforcecodex.com

Frequently asked questions

Can I use the same Account hierarchy for HCPs and patients in Life Sciences Cloud?

You can, and you will regret it. The two populations have distinct consent regimes and retention requirements, so a shared hierarchy forces every sharing rule, retention job and consent check to re-derive that distinction at runtime from a record type, and any one of those derivations can be wrong in an audit.

Does Agentforce respect field-level security and sharing rules by default?

Not on its own. Every agent runs as a running user that determines what it can access, new agents start with no permissions, and actions inherit permissions from the referenced Apex, Flow or Prompt Template. Enforcement lands in the automation you write behind the action.

What replaces WITH SECURITY_ENFORCED in API version 67.0?

Use WITH USER_MODE. WITH SECURITY_ENFORCED no longer compiles at API 67.0 and later, and WITH USER_MODE is the better clause anyway: it handles polymorphic fields, applies to every clause including WHERE, and reports all FLS violations instead of only the first.

When should I federate data in Data Cloud instead of storing it in Salesforce?

Federate when the org needs to reason over a dataset it never edits, such as market feeds or large external reference sets. Store or synchronise when users edit the data, when automation drives off it, or when offline access and heavy local querying are required.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment