Tag: Error Prevention

  • Flow orchestration for agents: handing a multi-step process to Agentforce safely

    An agent that calls one Apex action is easy to reason about. Give it a clear input, get a clear output, done. The trouble starts when you hand Agentforce something with five steps, two approval gates, and three ways it can fail halfway through. That’s not an action anymore. That’s a process, and Flow is where most Salesforce teams are reaching to let an agent run one safely.

    I’ve seen this go wrong two ways before it goes right. Either the Flow gets built the way it always has, screen-free but assuming a predictable caller, and the agent trips over edge cases nobody designed for. Or the Flow gets treated like a black box: wire it to an action, hope the agent passes sane inputs, and find out in production what “sane” actually meant.

    Why this isn’t just another autolaunched Flow

    A Flow built for a scheduled job or a button click has one caller, and that caller is predictable. A human clicking a button has already read the screen. A batch job runs against records that already passed a filter. Agentforce doesn’t give you either guarantee. It decides to invoke the Flow based on a conversation, so the inputs it hands over are only as good as the prompt, the topic instructions, and whatever grounding data it pulled that turn.

    That changes what the Flow needs to do before it does anything else: trust nothing coming in. Validate every input the way you’d validate a public API, not the way you’d validate a field that’s already constrained by a page layout and a validation rule.

    Three things to design before the happy path

    An input contract the agent can’t violate by accident

    Define exactly what the agent is allowed to pass, with types, required fields, and value ranges, as part of the action’s input parameters, not buried in Flow logic three screens deep. If a field should only ever be one of four picklist values, constrain it at the action level. An agent that free-types “urgent” into a priority field expecting “High” will do that eventually. Catch it at the boundary, not in a decision element nobody will debug at 9pm.

    A way to stop partway through without leaving a mess

    Multi-step Flows fail in the middle. A record updates, a callout times out, the fourth element in the chain throws. In a human-triggered Flow, a failure usually means the person tries again or logs a ticket. An agent-triggered Flow needs the same recovery path, except nobody’s watching it happen in real time. Build in checkpoints: a status field on the driving record that tracks which step last completed successfully, so a retry, whether triggered by the agent, a scheduled cleanup job, or a human reviewing a log, can pick up from where it actually broke instead of re-running steps that already succeeded.

    Idempotency, because the agent will call it twice

    Conversational agents retry. A timeout, a dropped connection, a user who asks the same thing two different ways in one session, and your Flow gets invoked again with the same or similar inputs. If step two creates a case and step four sends an email, a second run shouldn’t create a second case and send a second email. Check for an existing record before creating one. This is ordinary integration hygiene, and it matters more here than it did when the only caller was a trusted internal system.

    Where approval steps actually belong

    If the process needs a human sign-off before something happens, a refund over a threshold, a case escalation on a VIP account, anything touching financial data, don’t let the agent decide whether to pause for approval. Build the approval gate into the Flow itself, triggered by the data, not by the agent’s judgment about when something feels risky. The agent should know it can submit a request. It shouldn’t be the thing deciding whether that request needs a second pair of eyes. That decision belongs in business logic you control, versioned and testable, not in a prompt that someone can reword next week without telling you.

    Logging that helps you debug an agent, not just a Flow

    Standard Flow error emails and debug logs were built for admins debugging admin-triggered automation. When Agentforce is the caller, you need to know what the agent was asked, what it decided to pass in, and what the Flow did with it, all three, in one place you can actually query. I log the action name, the full input payload, the Flow interview GUID, and the final status to a custom object rather than relying on native fault emails. When something breaks three days later and the only report you get is “the agent said it couldn’t finish,” that log is the difference between a five-minute fix and a long afternoon reconstructing a conversation from debug logs that have already rolled off.

    Test it like an API, not like a form

    The usual Flow testing habit is to run it against a few representative records and eyeball the result. That’s not enough once an agent is the caller. Test it the way you’d test an integration endpoint: valid inputs, missing optional fields, wrong data types, boundary values, and the same call fired twice in a row. Where you can, write test invocations of the action itself, not just the Flow, that simulate what Agentforce would send on a bad day. A bad day is exactly when this will matter.

    The real shift

    None of this is new Flow capability. It’s the same orchestration tool doing what it always did, built with a different caller in mind. The teams getting this right aren’t running the fanciest Flow. They’re the ones who stopped assuming the caller is trustworthy by default and designed the contract first. Once that contract holds, handing a multi-step process to an agent stops being a risk you’re managing and starts being just another integration you’ve already solved before.

  • Giving an agent permission to act: designing the guardrails before Agentforce gets write access

    Every Agentforce rollout starts the same way: read access first, a few topics, a demo that goes well. Then someone asks the obvious next question. Can it actually update the case? Can it process the refund, not just look it up? That’s the moment the project gets real, and it’s also the moment most teams realise they don’t have an answer ready.

    I’ve sat in that meeting more than once. The agent works fine in read-only mode because read-only mode forgives almost everything. A bad query returns the wrong record and someone notices on screen. Write access doesn’t forgive. A bad write closes the wrong case, double-refunds a customer, or fires an email to the wrong contact, and by the time anyone notices, it’s already happened.

    Why write access changes the risk profile

    A human agent who makes a mistake usually makes one mistake. They’re slow, they second-guess themselves, and they ask a supervisor when something feels off. An AI agent with write access and a bug in its reasoning can make the same mistake a hundred times in the time it takes you to notice the first one. That’s the actual argument for guardrails: not that the agent is untrustworthy in some abstract sense, but that its error mode is different in kind from a human’s. Scale is the risk, not intent.

    This is why “it worked in the demo” tells you almost nothing about whether it’s safe to give an agent write access. The demo ran one happy path, once, with someone watching. Production runs every path, continuously, with nobody watching the individual transaction.

    The guardrails that actually matter

    Scope the permission set to the job, not the object

    The standard Salesforce instinct is to grant edit access on a case or an account and call it done. Don’t. Build a permission set that matches exactly what the agent’s topic is meant to do: update status on a specific case type, not edit any field on any case. If the agent’s job is to update delivery status on an order, it has no business editing the billing address on that same record, even though both fields sit on the same object. Narrow the permission set until the agent literally cannot do the thing you didn’t intend it to do, rather than trusting it won’t try.

    Put validation in the data layer, not the prompt

    A system prompt that says “don’t process refunds over $500” is an instruction, not a control. Agents misread instructions, and a cleverly worded customer message can talk an agent out of a soft rule faster than you’d expect. The actual limit belongs in a validation rule, a flow, or an Apex trigger on the object itself, somewhere the agent can’t negotiate with it. Let the agent attempt the action. Let the platform reject it if it breaks the rule. The agent should never be the only thing standing between a customer and a bad outcome.

    Build an approval step for anything you can’t undo

    Some actions are cheap to reverse: updating a case description, adding a note, re-routing a routine enquiry. Others aren’t: a refund, a contract change, anything that sends an external communication. For the second category, the agent should request the action and a human should approve it before it fires, at least until you’ve got enough production history to trust the pattern. This slows the agent down on the actions that matter and leaves it fast on the ones that don’t, which is the right trade.

    Log what it did and why, not just that it did something

    Standard field history tells you a value changed. It doesn’t tell you which topic triggered the change, what the customer said that led to it, or what the agent’s reasoning was at the point of action. Without that, every incident review turns into guesswork. Set up logging that captures the triggering input, the action taken, and a short reasoning trace, and keep it somewhere a support team can query quickly. The first time an agent does something wrong in production, you’ll want this a lot faster than you’ll want to build it.

    Testing before the switch flips

    Testing an agent with write access isn’t the same exercise as testing a flow. A flow does the same thing every time you give it the same input. An agent can take a slightly different path through the same conversation twice, so a single pass of test cases won’t tell you what you need to know. Run the same scenario multiple times with small variations in phrasing and check that the outcome stays within bounds, not that it’s identical. Then run the scenarios you hope never happen: an angry customer trying to talk the agent into an exception, a request that sits right on the edge of what the permission set allows, a malformed input that a human would recognise as odd. If the agent’s write access only gets tested on clean, polite inputs, you’ve tested the demo, not the job.

    What skipping this actually costs

    I’ve seen teams treat guardrails as the thing you add once the agent is working, not before. That ordering is backwards, and it’s backwards because of how these failures surface. A flow with a bug usually fails loudly and immediately. An agent with a subtly wrong permission set or a missing validation rule can run correctly for weeks and then fail on exactly the input nobody tested for, at a point where it’s already touched hundreds of real records. Retrofitting guardrails after that happens means an incident review, a rollback, and a much harder conversation with whoever owns the business outcome than the one you’d have had by building the guardrails first.

    None of this is an argument against giving Agentforce write access. It’s an argument for treating write access as a separate decision from read access, with its own permission design, its own validation layer, its own approval points, and its own test plan. Get that sequencing right and the agent earns broader access on a track record you can actually point to. Skip it, and you’re trusting a demo to predict production, which it never does.

  • Retiring old automation: the unglamorous work that pays off

    Every Salesforce org I’ve worked in has automation that nobody can explain. A process builder that fires on a field update from 2019. A validation rule that blocks a scenario nobody’s hit in three years. A trigger with a comment that says “TODO: remove after migration” and a commit date from a sprint that ended a long time ago. Nobody built it to be permanent. It just never got removed.

    Why dead automation never dies

    Admins and developers rotate through orgs faster than the automation does. The person who wrote that process builder left the team eighteen months ago. Whoever inherited the org after them didn’t touch it because it wasn’t broken, and “not broken” gets mistaken for “needed.” Nobody wants to be the one who deletes something and breaks a quarter-end report they didn’t know existed.

    So it stays. And it compounds. Every new flow has to account for what’s already running on that object. Every release review takes longer because nobody’s confident what fires in what order, or in what sequence. Governor limits get tighter not because the business grew, but because three generations of automation are all doing slightly overlapping work on the same records.

    I’ve seen this most clearly in case management. A contact-centre team I worked with had five separate pieces of automation touching the Case object: two process builders, a flow, an Apex trigger, and a workflow rule that predated all of them. Three were still firing. Two hadn’t done anything useful in over a year, but they still ran, still consumed execution time, and still showed up as “something to check” every time someone touched a case field.

    Finding out what’s actually still doing something

    Before you can retire anything, you need evidence, not memory. A few places worth checking:

    • Setup Audit Trail tells you when something was last changed. It won’t tell you whether it still matters, but it’s a starting point for dating the automation.
    • Field history tracking on the fields an automation updates. If a flow is supposed to set a field and that field hasn’t changed in six months, the flow either isn’t firing or isn’t doing what it was built for.
    • Debug logs with flow and process builder entries enabled, run over a representative week. This is the most reliable signal: if something doesn’t show up across a week of normal business activity, it’s either rare or redundant.
    • Flow Trigger Explorer, on orgs that have consolidated onto Flow, shows execution order on an object. It won’t catch Apex triggers or Process Builder, so check those separately.

    None of these on their own gives you certainty. Combine them. An automation that hasn’t changed in two years, doesn’t touch any field that’s changed recently, and doesn’t show up in a week of debug logs is a strong candidate. One signal alone usually isn’t enough to act on.

    Talk to the business before you touch anything technical

    The tooling tells you what’s firing. It doesn’t tell you why someone built it. Ask the team that works the object every day whether a specific case status, a specific email alert, or a specific field update actually matters to them. Sometimes the answer is “we stopped using that process two reorgs ago and nobody updated Salesforce.” Sometimes it’s “yes, that one’s load-bearing, leave it alone.” You need both kinds of answers, and only people can give you the second kind.

    Disable before you delete

    Don’t delete automation on day one, even once you’re fairly confident it’s dead. Deactivate it instead, whether that means unchecking “active” on a process builder, deactivating a flow version, or wrapping an Apex trigger’s logic behind a feature toggle. Leave it deactivated for a full business cycle if you can, long enough to hit month-end, quarter-end, and any seasonal process that only runs a few times a year.

    If nothing breaks and nobody notices, you’ve got your proof. Document what you deactivated, when, and why, then schedule the actual deletion for a later release. That gap between “disabled” and “deleted” is what turns this from a guess into a defensible decision. It’s also the step most teams skip, because deleting feels more satisfying than waiting.

    Start small, and keep a list

    Don’t try to clear a decade of automation debt in one release. Pick the most isolated piece first, something like a validation rule with no dependencies, or a workflow rule that only sends an email nobody reads. Prove the process on something low-risk before you go anywhere near a trigger that fifteen other pieces of logic depend on.

    Keep a running kill list as part of the backlog, not a separate cleanup project that gets deprioritised the moment a client asks for something new. Retire one thing most sprints. It won’t show up in a demo, and nobody’s going to thank you for it in a stakeholder meeting. But six months of steady retirement does more for the health of an org than any single new feature, because it’s the difference between every future enhancement fighting through a decade of accumulated logic and every future enhancement landing on a clean surface.

    What it actually buys you

    The payoff isn’t visible in a single release. It shows up over time: fewer “why did this fire twice” tickets, faster impact analysis when someone proposes a change, and a release review that takes an hour instead of an afternoon because the team can actually hold the automation model in their heads. None of that is glamorous. All of it is what separates an org that gets harder to change every year from one that stays workable no matter how long it’s been in production.

  • Change management for Salesforce releases: why “it works in sandbox” isn’t enough

    I’ve watched a deployment pass every test in sandbox, get signed off by UAT, go to production on a Friday afternoon, and break order processing for six hours. Nobody did anything wrong in the testing phase. The sandbox just wasn’t production, and the gap between the two is where most Salesforce release failures actually live.

    This isn’t an argument against testing in sandbox. It’s an argument for treating “it passed in sandbox” as the start of your confidence, not the end of it.

    What sandbox actually proves

    A sandbox proves your code compiles, your Apex tests pass, your Flow doesn’t throw an error on the records you fed it, and the three people who clicked through UAT didn’t find anything obviously wrong. That’s real value. It’s also a narrow slice of what production will throw at your release.

    Data shape and volume

    Most sandboxes are partial copies, refreshed months ago, seeded with a fraction of production’s record count. A trigger that handles 200 test records cleanly can hit governor limits against 50,000 real ones. A report that renders instantly against a thin dataset can time out against three years of case history. I’ve seen validation rules pass clean in sandbox because the sandbox simply didn’t have the edge-case data combinations that exist in a mature production org, duplicate record types from an old migration, fields left blank by an integration that used to populate them, status values nobody uses anymore but that are still sitting on live records.

    Integration behaviour

    Sandbox integrations are usually pointed at test endpoints, mocked responses, or nothing at all. Your middleware might behave completely differently when it’s talking to the real payment gateway, the real marketing platform, or the real legacy system on the other end. Callout timeouts, rate limits, and authentication token refreshes are exactly the kind of thing that only shows up under real traffic patterns, not a UAT script run by three people over two days.

    User behaviour under real load

    UAT testers follow the happy path you gave them. Real users don’t. They open fifteen tabs, paste data from Excel with hidden characters, hit buttons twice because the page felt slow, and use the org in ways the requirements document never anticipated. None of that shows up until go-live, and by then it’s not a bug report, it’s a support queue.

    Build a release process, not a hope

    The fix isn’t more sandbox testing. It’s treating release management as its own discipline, with its own checks, separate from “did the feature work.”

    Get your environment strategy right first

    If you’re still deploying from a single developer sandbox straight to production, that’s the first thing to fix. A reasonable baseline for an enterprise org looks like: source-tracked developer sandboxes or scratch orgs for build, an integration sandbox for combined testing across teams, a full or partial copy sandbox refreshed regularly for UAT and performance testing, and production. Each stage exists to catch a different class of problem. Skipping a stage doesn’t save time, it just moves the discovery of the problem later and more expensive.

    Automate what should never depend on memory

    Manual change sets, clicked together by whoever’s free that week, are where deployments silently drop a permission set or forget a custom label. A CI/CD pipeline, even a modest one built on Salesforce DX and a free GitHub Actions runner, forces every deployment through the same validated steps: run all tests, check code coverage, validate against the target org before deploying, and keep a record of exactly what went out and when. The point isn’t tooling for its own sake. It’s removing the chance that Friday’s deployment depends on someone remembering a manual step from three releases ago.

    The checks that catch what sandbox misses

    Test against real data volume

    Before a release that touches triggers, flows, or batch logic, run it against a full or recent partial copy sandbox, not a seeded one. If that’s not available, at minimum export enough representative production data to simulate real volume and genuinely messy field values. A load test that takes an afternoon is cheaper than an incident that takes a night.

    Check permission and profile parity

    A feature can work perfectly for the system administrator profile used in sandbox testing and fail completely for the actual end-user profile in production, because a field-level security setting, a permission set assignment, or a sharing rule differs between the two environments. Test with a cloned user matching the real target profile, not your own admin login.

    Write the rollback plan before you deploy, not after it breaks

    Know in advance exactly how you’d undo this release: which metadata to retract, whether any data migration needs reversing, who has the authority to pull the trigger on a rollback at 2pm on a Tuesday. If you can’t answer that in one sentence before deployment, you’re not ready to deploy, regardless of how clean sandbox looked.

    Watch the first hour in production like it’s still testing

    The deployment finishing isn’t the finish line. Monitor error logs, check that scheduled jobs and integrations fired correctly, and have someone specifically watching for the first hour rather than assuming silence means success. Most production incidents I’ve seen were visible in the debug logs within minutes. Nobody was looking.

    What this looks like in practice

    On a typical enterprise Salesforce org I’ve worked with, the releases that went smoothly weren’t the ones with the most UAT sign-offs. They were the ones where someone had deliberately asked “what does production have that sandbox doesn’t,” and then tested against that gap specifically: real data volume, real integration endpoints, real user profiles, and a rollback plan written down before the deployment window opened.

    “Works in sandbox” is a necessary condition for a safe release. It was never a sufficient one. Treat the gap between the two environments as a known risk to manage, not a surprise to react to, and most of your worst release days stop happening.