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.
Encryption, consent, and the offline trap
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.
Leave a Comment