← All articles

GTM Engineering

Before You Give an AI Agent Write Access, Build the Blast Radius

An LLM with a Salesforce login and no guardrails can rewrite 160,000 accounts before lunch. Here is the three-layer control stack that runs before the agent does.

· 11 min read

The demo is intoxicating. An agent reads a messy account, decides the industry is wrong, and fixes it. Now picture that same agent, on a bad prompt or a hallucinated rule, deciding 160,000 accounts are wrong and fixing all of them before you finish your coffee. There is no undo button on the API. The agent had a valid login and full write access, so Salesforce did exactly what it was asked. That is not the agent misbehaving. That is you handing a stranger the keys and being surprised they drove.

The teams running five or more AI use cases in production are rare enough to feel like outliers, so the pressure to ship agents is real. Almost everyone ships the agent and skips the thing that makes the agent safe: the control layer. The control layer answers one question you must answer before write access, not after the incident. What is the blast radius? What is the maximum damage this thing can do in one bad run, and what stands between it and that damage?

That answer is a three-rung stack, and the rungs compose bottom-up. Permissions cap what the agent can reach. A queue puts a seam between decision and effect. Rollback makes every applied change reversible from data you already have. You build them in that order, and each one only matters because the one below it holds.

The three-layer control stackPermissions cap it, the queue governs it, rollback reverses it
  1. L3Dry-run, risk gate, and rollbackminutes

    Render all proposals as before-and-after pairs with a count; the count is your first alarm. Route the low-confidence, high-volume quadrant to a human. Because current values are captured at proposal time, one flow reverses any applied batch in minutes.

  2. L2The agent proposes, the queue disposesthe seam

    The agent writes to Agent_Action__c, not to the record. Every change becomes a proposed-action row: target, field, current value, proposed value, confidence, status, rationale. The seam between decision and effect is where every governance control lives.

  3. L1Scope the permission surface2 fields

    A dedicated integration user with field-level security granting edit on exactly the fields the job needs, read on the rest, no create, no delete. The platform enforces the lane on every write, so a jailbreak or a hallucination cannot leave it. Build this one even if you build nothing else this week.

160K
Accounts a broad-login agent can rewrite in one run
2 fields
What a scoped integration user can touch
minutes
Time to full rollback when current values are captured

L1: shrink the permission surface

Before any prompt engineering, before any tool definitions, the agent gets a locked-down identity. Not your admin login. Not a service account with System Administrator. A dedicated integration user whose permissions are the exact minimum for the job.

If the agent’s job is enriching account industry and employee count, then its permission set grants edit on Industry and NumberOfEmployees and read on the rest. Field-level security does the enforcement. The agent cannot write Owner, cannot touch Amount, cannot delete a record, because the FLS says no at the platform layer, no matter what the model decides. You are not trusting the prompt to stay in its lane. You are building a lane it cannot leave.

Permission Set: AI_Enrichment_Agent
  Object: Account   Read: Yes   Create: No   Delete: No
  Field: Industry              Edit: Yes
  Field: NumberOfEmployees     Edit: Yes
  Field: Owner, Amount, ...    Edit: No   (FLS blocks writes)

Then lock the door it came through. The connected app the agent authenticates through gets IP restrictions, a scoped OAuth policy, and admin-approved users pre-authorized, so a random token cannot mint a session. The agent’s reach is now defined by config you can read, not by how well you worded the system prompt.

L2: the agent proposes, the queue disposes

The second rung: the agent does not write to Salesforce. It writes to a queue. Every change the agent wants to make becomes a proposed-action record, not a live mutation.

Agent_Action__c
  Target_Record__c   (Lookup)   001...
  Field__c           (Text)     "Industry"
  Current_Value__c   (Text)     "Software"
  Proposed_Value__c  (Text)     "Financial Services"
  Confidence__c      (Number)   0.62
  Status__c          (Picklist) Pending / Approved / Rejected / Applied
  Rationale__c       (Long Text)

Now there is a seam between decision and effect, and every governance control lives in that seam.

Dry-run first. Before anything applies, generate the full diff. 4,000 proposed changes? You see all 4,000 as before-and-after pairs. The count itself is the first alarm. If the agent proposes to change industry on 90 percent of accounts, something is wrong with the agent, and you learn it from the queue instead of from the field history report after the fact.

Approval-gate by risk. Low-confidence or high-volume changes route to a human. High-confidence, low-blast changes can auto-apply. The threshold is yours. The point is that the dangerous quadrant, low confidence and high volume, cannot pass without a person. Here is the routing quadrant.

Low volumeHigh volume
High confidenceAuto-applyHuman review (batch alarm)
Low confidenceHuman reviewBlocked until a person clears it

A concrete rule set that lives in the apply job:

// Routing the queue by risk before anything touches the record
for (Agent_Action__c a : queue) {
    if (a.Confidence__c >= 0.9 && String.isBlank(a.Current_Value__c)) {
        a.Status__c = 'Approved';        // high conf, filling a blank, low blast
    } else if (a.Confidence__c < 0.7 || batchSize > 500) {
        a.Status__c = 'Pending';         // dangerous quadrant, waits for a click
    } else {
        a.Status__c = 'Pending';         // default to human when unsure
    }
}

Rollback is designed in, not hoped for. Because Current_Value__c is captured at proposal time, reverting is mechanical. One flow reads the applied actions and writes Current_Value__c back. You are never reconstructing what a field used to hold from field history at 11pm. The undo is a record you already have.

Direct write versus the control layer

Agent with a broad login Agent behind the control layer
Identity Admin or SysAdmin service account Scoped integration user, 2 editable fields
What it can write Anything, any object Only what FLS permits
How changes land Direct API mutation Proposal into Agent_Action__c queue
Before it applies Nothing, it already applied Dry-run diff, volume alarm, approval gate
Rollback Reconstruct from field history, if lucky One flow, minutes, from Current_Value__c
Worst-case blast radius 160,000 accounts A bounded batch, all reversible
Same model, same use case. One has a blast radius of the whole org. The other has a blast radius you can name.

L3: the blast-radius drill, run on a real batch

Here is the exercise I run before an agent gets write access. It is a whiteboard, not code. For a given agent, fill in the worst case, then check the numbers against the queue. Take an enrichment run over 4,700 in-scope accounts where the model, on a bad rule, decides most of the industries are wrong.

Blast-radius questionWorst plausible answerWhat stops itAccounts applied
Max records touched in one runEvery in-scope account, 4,700Query cap plus batch bound4,700 possible
Max fields alteredOnly the FLS-edit list, 2 fieldsL1 permission setIndustry + employees
Worst outputFlip industry on 90% of accounts, 4,200L2 dry-run count + volume alarm0
Employee-count overwrite3,800 recordsHigh-volume quadrant, human gate0
Time to full rollbackn/a, nothing appliedL3 rollback flow if it hadminutes

The last two columns are the payoff. The agent proposed 4,200 industry flips and 3,800 employee-count overwrites, and zero of them applied, because the volume tripped the human gate at L2 before any write reached the record. The drill is the framework rehearsed against real counts: every high number in the worst-case column has a rung of the stack sitting under the zero next to it.

A runaway agent, caught at the queue
The agent proposed changes to 90% of accounts. The dry-run count is the alarm. Zero of these applied, because the volume tripped the human gate before the write.
View as table
ItemValue
Industry flip4,200 accts
Employee count3,800 accts
Total in-scope4,700 accts

An agent that runs behind a locked permission set, proposes into a queue, shows its diff, waits for approval on the risky changes, and can be reverted from data you already captured has a blast radius you can name and survive. An agent with a broad login and a direct write has a blast radius of your entire org.

The build order

The control layer, in the order I build it
  1. 1

    Scoped integration user + permission set

    Edit on exactly the fields the job needs, read on the rest, no create, no delete. FLS is the enforcement. This caps the blast radius before a single prompt exists.

  2. 2

    Lock the connected app

    IP restrictions, scoped OAuth policy, admin-approved users pre-authorized. A stray token cannot mint a session. The reach is config you can read.

  3. 3

    The Agent_Action__c queue

    The agent writes proposals, not mutations. Target, field, current value, proposed value, confidence, status, rationale. This is the seam every control lives in.

  4. 4

    The dry-run diff

    Render all proposed changes as before-and-after pairs with a count. The count is your first alarm. 90 percent of accounts flagged means the agent is wrong, and you see it here.

  5. 5

    Risk-based approval

    Auto-apply the high-confidence low-blast quadrant, route everything else to a human. The dangerous quadrant, low confidence high volume, never passes without a click.

  6. 6

    The rollback flow

    One flow reads applied actions and writes Current_Value__c back. Because you captured current values at proposal time, undo is mechanical and takes minutes.

Why the queue is worth the extra hop

The objection I hear most is that the queue adds latency and a table to maintain, so why not let the agent write directly and review the field history afterward. The answer is in the word afterward. Field history tells you what changed after it changed, which is useful for a forensic reconstruction and useless for prevention. The queue moves the review to before the write, and that single reordering is the entire value. A dry-run diff you read before applying is prevention. A field-history report you read after applying is an autopsy.

The queue also changes who can own the agent safely. Direct write demands that whoever runs the agent trusts the prompt, the model, and their own query scoping perfectly, every run, forever. The queue demands only that a reviewer can read a diff. That is a far lower bar, and it means a RevOps admin can own the agent without being an AI engineer, because their job is to look at 40 proposed changes and approve or reject, not to reason about what a language model might do under an adversarial input. You have converted an unbounded trust problem into a bounded review task.

And the queue is where telemetry lives for free. Every proposal is already a row with a before value, a proposed value, a confidence, and a rationale. That is the action log the pilot survival checklist demands, produced as a byproduct of the control design rather than bolted on. The rollback flow reads the same rows. One table earns its keep three times: as the review surface, as the telemetry log, and as the undo button.

The honest cost

None of this is free, and pretending otherwise is how the control layer gets skipped. Building the scoped user, the permission set, the connected-app lockdown, the queue object, the dry-run renderer, the approval routing, and the rollback flow is real work, call it a week for a first agent. The direct-write version is an afternoon. That gap is exactly why teams skip the control layer, and exactly why 160,000-account incidents happen. The afternoon version has a blast radius of your whole org. The week version has a blast radius you can name, bound, and reverse. You are not paying for features. You are paying for the ability to survive the agent’s worst run, and that is the only version worth putting into production.

Before your next agent ships, draw its blast radius on one page and answer the what-stops-that line for every field it can write. If you cannot answer it, you have not built the control layer yet, and the agent is not ready no matter how good the demo looked. This pairs with the pre-launch checklist that keeps pilots alive past 90 days.

ai salesforce governance

Keep reading

One email. Every week.

One email a week: a system I built or broke, with the config, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.

The newsletter opens soon.

Connect a provider in src/config.ts