An Agentforce agent engineered so it cannot fabricate
A Salesforce and Agentforce agent that structures CVs, answers grounded questions about candidates, and ranks them explainably — where Apex verifies every claim against the source text before it is stored or shown.
- Context
- Independent build — technical assessment
- Role
- Sole designer and engineer
- Period
- 2026
- Systems
- Agentforce ⇄ Invocable Apex ⇄ Claude API
At a glance
The output is usable for a decision about a person, because every claim in it can be traced to text in the CV.
- A fact with no supporting quote never reaches the database.
- An answer that cannot be verified is refused rather than guessed.
- Missing, inferred, and conflicting information is visible and countable per candidate.
Jump to
Overview
What this was
Anyone can prompt a model to pull skills out of a CV. The hard part is making the output trustworthy. This build treats grounding, uncertainty, and explainability as structure that Apex verifies — not as behaviour the prompt requests and hopes for. The prompt asks for honest output; Apex proves it before anything is stored or displayed. A claim that cannot point at CV text is dropped, however confident the model sounded.
Business context
Built as a technical assessment, and engineered as production work rather than a demonstration. It is included here because it is the clearest available example of a design principle that applies well beyond recruitment: when a system’s output will inform a decision, the parts that must be reproducible belong in code you can test, not in a component whose output varies between runs.
Delivery timeline
- 01
Framing
Established that the hard problem is trust, not extraction.
- 02
Data model
Modelled uncertainty as rows and fields rather than prose.
- 03
Service seam
Put the model behind one interface with a mock double.
- 04
Grounding gate
Built the verification that drops unsupported claims.
- 05
Scoring
Rubric as metadata; Apex computes and ranks.
- 06
Proof
58 Apex tests, with the trust guarantees as the assertions.
Business problem
A recruiter cannot act on an AI summary they have no way to verify. The failure mode is not a missing skill — it is a confident, invented one.
Fabrication is indistinguishable from extraction
A model states an invented qualification in exactly the same register as a real one. Without verification, the reader cannot tell them apart.
Uncertainty gets flattened into prose
Missing, inferred, and contradictory information ends up buried in a summary paragraph, where it cannot be filtered, counted, or surfaced.
A model-produced score explains nothing
A number the model asserts is not reproducible and cannot be traced to a criterion — which makes it useless for a decision about a person.
No audit trail on a consequential decision
Candidate evaluation needs to be reviewable after the fact, including what the system was told and what it answered.
Challenges
What made this harder than it looks
- 01
A prompt cannot enforce honesty
Asking a model not to fabricate reduces fabrication; it does not prevent it. Any guarantee has to be enforced somewhere deterministic.
- 02
Uncertainty has more than one shape
Missing, inferred, and conflicting are genuinely different states with different consequences, and collapsing them into "unknown" loses the distinction that matters.
- 03
A score must be explainable to be usable
Every point needs to trace to a criterion, a weight, an evidence quote, and a certainty level — otherwise it cannot be defended.
- 04
The guarantees had to be provable without a live callout
Anti-hallucination claims are worthless if they can only be checked by hand against a live API.
- 05
Weak matches must not read as rejections
"We do not know enough about this candidate" and "this candidate is weak" are different conclusions, and the system has to distinguish them.
Architecture
How the system fits together
Three layers under one rule: the model reasons, and Apex enforces honesty and does anything deterministic. Agentforce is orchestration only — the layer with the least control holds the least responsibility.
- Recruiter to Agentforce topic
- Agentforce topic to Invocable actions: delegates
- LWC UI to CVAgentController
- CVAgentController to Invocable actions: same actions
- Invocable actions to ILLMService
- ILLMService to Claude API: Named Credential
- Invocable actions to Grounding gate: model output
- Grounding gate to Candidate + Fact: verified only
- Invocable actions to Agent_Log__c: audit
End-to-end flow
- 01
The agent classifies intent
An Agentforce topic interprets the recruiter’s natural-language request and delegates to an invocable Apex action. It holds no business logic.
- 02
An action calls the model behind an interface
Every action reaches the model through ILLMService, so the implementation is swappable and mockable.
- 03
The grounding gate runs
Model output is verified against the source CV text. Any claim whose cited quote does not verify is dropped or suppressed before it is stored or shown.
- 04
Apex computes anything deterministic
The model judges one candidate against one criterion. Apex computes the score and performs the ranking.
- 05
Verified data is persisted and audited
Only verified facts reach the data model, and every action writes to an audit log.
- 06
The UI uses the same actions
The Lightning Web Component controller delegates to the identical actions the agent uses, so the two cannot diverge.
Technologies
Salesforce
- Agentforce (agent + topic)
- Invocable Apex actions — five
- Apex service layer with a mockable interface
- Custom objects — Candidate, Fact, Bookmark, Agent Log
- Custom Metadata — rubric, scoring config, LLM config
- Lightning Web Components — three
- Roll-up summaries for conflict and unknown counts
- Permission set for scoped access
Integration
- Named Credential
- External Credential with custom auth headers
- Claude API
Quality
- 58 Apex tests across 9 test classes
- HttpCalloutMock
- Mock LLM service double
Solution design
The choices that shaped everything else
Uncertainty is data, not text
Every extracted claim is its own Fact__c row carrying a certainty label — Stated, Inferred, Conflicting, or Missing — and a verbatim evidence quote. Uncertainty becomes queryable, roll-up-summable, and renderable rather than buried in a paragraph. The recruiter sees "2 conflicts, 3 unknowns" on the record without opening a single fact.
The certainty vocabulary cannot drift
The four certainty values are a restricted picklist that exactly mirrors the Apex validation vocabulary, so the database and the code cannot fall out of step.
Grounding is enforced on both the write and the read path
Ingestion drops any fact whose evidence field is blank. Querying and scoring verify each cited quote against the source text and suppress anything that does not match — so an answer is either evidenced or it is "Not found in the CV."
Uncertainty moves the number
Each criterion’s contribution is discounted by the certainty behind it — Stated 1.0, Inferred 0.6, Conflicting 0.4, Missing 0.0. Missing counts as zero but is surfaced as a gap to validate, never as a confirmed weakness, and is paired with a coverage signal so "we do not know enough" stays distinguishable from "this candidate is weak."
A bookmark is a self-contained snapshot
Saving an evaluation stores the score, reason, and a JSON snapshot as shown at evaluation time — so a past decision can be reviewed as it was made, not as it would be recomputed today.
Technical decisions
What was decided, why, and what it cost
Every decision below is paired with its trade-off. A design choice without a stated cost usually means the cost was not examined.
Enforce grounding in code rather than requesting it in the prompt
Rationale
A prompt is a request; a verification gate is a guarantee. Any fact or cited quote that does not verify against the source is dropped or suppressed regardless of how confident the output appeared.
Trade-off
Verification is a strict normalised substring check, which biases toward false negatives — a near-verbatim quote can be rejected. Deliberate: for a trust-first system, refusing when it could have answered is far safer than answering when it should not have.
The model judges; Apex computes and ranks
Rationale
The model assesses one candidate against one criterion and cites evidence. It never produces an overall score and never compares candidates. Apex computes score = Σ(match × weight × discount) and sorts. Every point therefore traces to a criterion, a weight, a quote, and a certainty.
Trade-off
More orchestration and more calls than asking for one overall judgment. That is the cost of a reproducible number.
Scoring policy lives in Custom Metadata
Rationale
Rubric weights, certainty discounts, and the strong-match threshold are data. A reviewer can read every number that produces a score without reading Apex, and tuning does not require a deployment.
Trade-off
Behaviour depends on records that must exist. Apex carries a fallback identical to the seeded values so runtime is unaffected if they do not.
Prompts live in a version-controlled Apex constants class
Rationale
They began in Custom Metadata for hot tuning — until a length-255 field silently truncated a ~2,200-character prompt on every deploy, producing rounds of apparent model misbehaviour that were really the model working from two sentences. In Apex they are diffable, atomic with the code that depends on them, and immune to that class of bug.
Trade-off
Changing a prompt now requires a deployment. Acceptable — prompts are logic, and logic belongs under review.
One interface for the model, with a mock double
Rationale
A single ILLMService seam is what makes the honesty guarantees testable without a live callout — and it is why the anti-hallucination claims are assertions in a test suite rather than statements in a README.
Trade-off
One layer of indirection between the action and the API call.
Isolate and stub the document extraction boundary
Rationale
PDF and DOC to text conversion is an external concern. Treating it as a stubbed boundary kept a half-working parser out of the core rather than weaving it through.
Trade-off
Binary uploads are not supported end to end; the component reads .txt and accepts pasted text.
My responsibilities
What I personally owned
- Sole designer and engineer — data model, architecture, prompts, Apex, LWC, and tests.
- Designed the Candidate Card model that represents uncertainty as structured data.
- Built the grounding gate that drops or suppresses any unsupported claim.
- Built the five invocable actions and the thin controller the UI shares with the agent.
- Designed the rubric-as-metadata scoring model with Apex-side computation and ranking.
- Wrote 58 Apex tests structured to prove the trust guarantees rather than to reach a coverage number.
- Documented the architecture, decisions and trade-offs, assumptions, and known limitations.
Implementation process
How it was actually built
- 01
Named the real problem first
Extraction was never the hard part. Framing the work as a trust problem is what produced a verification gate rather than a better prompt.
- 02
Modelled uncertainty before building anything
Deciding that certainty and evidence are fields on a row — not sentences in a summary — determined the shape of every layer above it.
- 03
Established the mockable seam early
Putting the model behind ILLMService from the start is what allowed the guarantees to be tested continuously rather than demonstrated manually.
- 04
Moved the score out of the model
The first design asked the model for an overall score. Replacing it with per-criterion judgment plus Apex arithmetic turned an opaque number into a traceable one.
- 05
Debugged a configuration failure that looked like a model failure
Rounds of apparent model misbehaviour turned out to be a 255-character metadata field silently truncating the prompt. The fix was architectural: prompts moved into version-controlled Apex.
- 06
Made the tests the argument
ungrounded_fact_is_rejected, fabricated_quote_is_suppressed, and fabricated_quote_collapses_to_a_gap are the three tests that back the central claim. The documentation points a reviewer at them directly.
Security considerations
What was protected, and how
The API key is never in source
Authentication runs through an External Credential principal and a Named Credential. The key is entered in the org and never committed.
Access is granted by permission set, not profile
A dedicated CV_Agent_Access permission set grants object and field access to the recruiter and to the agent user. There is no guest or external exposure.
The agent user is explicitly scoped
The agent user is granted Apex class access through the same permission set, so it can invoke only the intended actions.
Every action is logged
Agent_Log__c records the action, status, input, output, error, and duration — so an evaluation can be audited after the fact.
Candidate data stays inside the org boundary
CV text leaves the platform only on the single outbound path to the model API, through the credential store.
Business value
The output is usable for a decision about a person, because every claim in it can be traced to text in the CV.
- A fact with no supporting quote never reaches the database.
- An answer that cannot be verified is refused rather than guessed.
- Missing, inferred, and conflicting information is visible and countable per candidate.
- Every point in a score traces to a criterion, a weight, a quote, and a certainty level.
- Weak evidence is surfaced as a gap to validate in interview, not as a confirmed weakness.
- Scoring policy is readable and tunable as data, without a deployment.
- Every agent action is logged with input, output, and duration.
Lessons learned
What I took from it
Guarantees belong in the deterministic layer
Anything a system promises has to be enforced somewhere that cannot vary between runs. The prompt asks; the code proves.
A silent truncation is worse than a loud failure
A 255-character field quietly cutting a prompt cost several debugging cycles chasing model behaviour. Configuration that can silently discard part of its value is a design defect, not an operational detail.
Choose your error direction deliberately
The grounding check errs toward false negatives. That is a decision with a stated cost — fewer good answers — rather than an accident, and it is written down as such.
Model capability is an architectural constraint
A smaller model could not reliably hold the six-field extraction schema. Reliability came from schema-first prompt ordering, explicit negative instructions, and response prefill — design choices, not prompt polish.
Honest asymmetries are worth documenting
Single-candidate certainty is weaker than multi-candidate certainty, because one reads from verified rows and the other from the model. Stating that plainly is more useful than implying uniform strength.
Future improvements
What I would do next
Listing the known gaps is part of the handover. A system described as finished usually means nobody looked hard enough.
Span alignment instead of strict substring matching
The one genuine improvement. Today the grounding gate rejects a truthful answer whose quote is lightly paraphrased. A token-overlap check against the source with a high threshold would keep the guarantee — no answer without real support — while refusing fewer good answers. It is the one place where the safe choice visibly costs recall.
Improvement 1Real document extraction behind the existing boundary
The extraction seam is already isolated, so a proper PDF and DOC extractor can be dropped in without touching the core.
Improvement 2Per-role rubrics
The metadata design already allows multiple rubrics; only one default is modelled today. Per-role weighting is a configuration extension rather than a rewrite.
Improvement 3Component-level tests for the UI
The Apex controller is fully tested and the components hold no business logic, but Jest coverage would protect the rendering contract.
Improvement 4
Keep reading