🎬 Real-Life Example: The Restore Time That Was Never Promised
The Old/Bad Way: A team wrote: "If the outage is major, tell the customer a restore estimate, but only if it is confirmed." The agent regularly promised times that were not confirmed.
Why this fails: That is one English sentence asking the model to make a judgement call. The model weighed it against everything else in the prompt and sometimes got it wrong. Nothing was ever fixed before it was asked.
The New/Good Way:
- Run an action that returns confirmed
trueorfalse, plus the estimate. - Store both in variables.
- Branch on the boolean.
- The true branch includes a sentence containing the estimate. The false branch includes a sentence saying no estimate is available.
The payoff: The model can no longer promise an unconfirmed time, because the prompt it receives does not contain one.
Concept
- Prompt building is a sequence, and Salesforce documents it step by step.
- The prompt starts empty.
- Each logic line executes in order — increment a counter, run an action, set a variable, evaluate a condition.
- Each prompt line appends English text to the prompt being built.
- Conditional lines decide whether the prompt text under them is included at all.
- Only when the whole list has resolved is the prompt sent to the model.
- So a decision made at line three cannot be altered by anything the model does at line eleven.
- This is precisely why Agentforce can be trusted with eligibility and money.
prompt = ""
├─ run @actions.get_outage_status
├─ set @variables.is_confirmed = @outputs.confirmed
├─ append: 'Tell the customer the outage is being worked on.'
├─ if is_confirmed == True
│ └─ append: 'Give the estimate: {[email protected]}'
└─ if is_confirmed == False
└─ append: 'Say no estimate is available yet.'
→ send prompt to LLM
🧭 360 Card — How the Prompt Gets Built
- Rule: Decide in logic, explain in language. Anything that must be certain is settled before the prompt is sent.
- Gain: The model cannot contradict a decision it never participated in.
- Price: More logic lines to write and maintain than a single paragraph of instructions.
- Limits: Logic lines still obey Apex and action limits, including the 60-second timeout.
- Mirror — one long instruction paragraph: Faster to write, but fails unpredictably under pressure from a determined user.
- Later: This is the foundation of every guardrail and test assertion in upcoming posts.
- At volume: Fewer, sharper prompt lines mean shorter prompts, lower cost, and faster replies.
Core Q&A
Q: How do you stop an agent from inventing a warranty or eligibility answer?
🎯 Say this first: Run an action, store the result in a variable, and branch on it. The model only phrases an outcome that was already decided.
A: Make the decision before the model is involved.
- I call an action that returns a decided value — covered true or false, plus a plain-language reason.
- I store both in variables.
- Then I branch. Each branch appends different prompt text, but the verdict itself is already fixed.
- The model receives finished English and its only job is to phrase it kindly.
- The reason string matters here too. Without it, the model invents its own justification for a decision it did not make.
- The test is simple: ask the same question ten times and the verdict must be identical every time.
Follow-ups (Scenario-based)
Q1: The same question gives slightly different wording each time. Is that a bug?
A1: Not in itself. Wording variation is expected and both answers can be correct.
- What must never vary is the decided value — the verdict, the number, the eligibility.
- If those vary, the decision is still inside the model and needs moving into logic.
- This is exactly why test frameworks check routing, actions, and decided values rather than exact strings.
- Common mistake: Trying to force identical wording with more instructions. That fights the tool.
Q2: Where would you put a check that must run no matter what the model says?
A2: after_reasoning, because it runs on every request once reasoning completes.
- It holds logic only — no prompt text — so it is a genuine control rather than a request.
- One caveat: If the subagent transitioned away partway through, its
after_reasoningnever runs. - For anything critical, put the guard where it will actually execute, or in the subagent you transition to.