Your LLM approved a transaction that violates policy
You deployed an LLM to approve or reject loan applications. It uses RAG to fetch relevant policies, and it’s remarkably accurate — 95% approval rate matches your historical baseline.
Then it approved a loan that violates explicit policy: “No loans > $100k without 2 manager approvals.” The loan was $150k with 1 approval.
The LLM had the policy in context. It just didn’t apply it correctly.
This is where logic engines help. They’re not smarter than LLMs, they’re just deterministic. They can’t make judgment calls, but they can enforce rules.
The difference: Reasoning vs. Verification
Reasoning (what LLMs do):
- Read context
- Make probabilistic judgments
- Return a best guess
- Good at ambiguity, bad at hard rules
Verification (what logic engines do):
- Take facts and rules
- Derive conclusions mechanically
- Return “definitely true” or “definitely false”
- Good at hard rules, bad at ambiguity
The best systems use both.
Three types of logic engines
1. Prolog (logic programming)
Prolog is a language where you define facts and rules, then ask questions.
% Facts
manager(alice).
manager(bob).
loan_approved_by(loan_123, alice).
% Rules
requires_two_approvals(Loan) :-
loan_amount(Loan, Amount),
Amount > 100000.
has_required_approvals(Loan) :-
requires_two_approvals(Loan),
loan_approved_by(Loan, Manager1),
loan_approved_by(Loan, Manager2),
Manager1 \= Manager2.
can_fund(Loan) :- has_required_approvals(Loan).
can_fund(Loan) :-
\+ requires_two_approvals(Loan),
loan_approved_by(Loan, _).
Now query:
?- can_fund(loan_123).
false. % Because only 1 approval, needs 2
?- can_fund(loan_456).
true. % Because < $100k, needs only 1 approval
Strengths:
- Deterministic (Prolog proves whether something is true, doesn’t guess)
- Handles negation and complex logic
- Can backtrack and explore all solutions
Weaknesses:
- You must hand-code all rules
- Doesn’t handle ambiguity
- Slow on large datasets (but fast on small ones)
2. Datalog (logic queries)
Datalog is like Prolog but optimized for database queries.
% Facts (stored in database)
manager(alice).
manager(bob).
loan_approved_by(loan_123, alice).
% Rules
requires_two_approvals(Loan) :-
loan(Loan, Amount),
Amount > 100000.
has_required_approvals(Loan) :-
requires_two_approvals(Loan),
loan_approved_by(Loan, Manager1),
loan_approved_by(Loan, Manager2),
Manager1 != Manager2.
can_fund(Loan) :- has_required_approvals(Loan).
can_fund(Loan) :-
loan(Loan, Amount),
Amount <= 100000,
loan_approved_by(Loan, _).
% Query: Find all fundable loans
?- can_fund(Loan).
Datalog is to Prolog as SQL is to full logic programming. Simpler, faster, often enough.
3. Constraint solvers
For optimization problems (“find the best assignment”).
# Example: Assign salesreps to customers
from ortools.linear_solver import pywraplp
solver = pywraplp.Solver.CreateSolver('SCIP')
# Variables: assignment[rep][customer] = 1 if rep assigned to customer
assignments = {}
for rep in reps:
for customer in customers:
assignments[(rep, customer)] = solver.IntVar(0, 1, f'{rep}_{customer}')
# Constraints
# 1. Each customer gets exactly 1 rep
for customer in customers:
solver.Add(sum(assignments[(rep, customer)] for rep in reps) == 1)
# 2. No rep has more than 10 customers
for rep in reps:
solver.Add(sum(assignments[(rep, customer)] for customer in customers) <= 10)
# 3. Minimize travel distance
objective = solver.Objective()
for rep in reps:
for customer in customers:
objective.SetCoefficient(
assignments[(rep, customer)],
distance(rep, customer)
)
objective.SetMinimization()
# Solve
status = solver.Solve()
# Get optimal assignment
Hybrid: LLM + Logic engine
Pattern 1: LLM generates facts, logic engine verifies
User: "Should we approve this loan?"
↓
LLM reads application, extracts:
- loan_amount: 150000
- approvals: [alice]
- collateral: house
↓
Logic engine checks:
- requires_two_approvals(150000)? true
- has_two_approvals([alice])? false
- Result: DENY
↓
LLM explains: "This loan needs 2 manager approvals,
you have 1. Needs 1 more approval."
Pattern 2: Logic engine generates options, LLM ranks
Problem: Assign salesreps to customers (100 possibilities)
↓
Logic engine generates all valid assignments
(that satisfy constraints)
↓
LLM ranks them:
- Option A: Low travel distance but unbalanced workload
- Option B: Balanced workload, medium travel distance
↓
LLM recommends: "Option B is better because..."
Real example: Compliance checking
Setup:
- Rules database: Hundreds of regulatory rules (Prolog/Datalog)
- Facts database: Transaction details, customer info
- LLM: Extracts facts from applications, explains results
Transaction: “Transfer $500k to new beneficiary in Belarus”
LLM extracts facts:
transaction_amount(tx_001, 500000).
transaction_type(tx_001, wire_transfer).
beneficiary_country(tx_001, belarus).
customer_aml_status(tx_001, verified).
Logic engine runs rules:
% Rule 1: Large transfers need AML check
triggers_aml_check(Tx) :- transaction_amount(Tx, Amt), Amt > 250000.
% Rule 2: High-risk countries need extra scrutiny
high_risk_country(belarus).
triggers_enhanced_check(Tx) :-
beneficiary_country(Tx, Country),
high_risk_country(Country).
% Rule 3: Enhanced check requires approval
requires_approval(Tx) :- triggers_enhanced_check(Tx).
requires_approval(Tx) :- triggers_aml_check(Tx).
% Query: Is tx_001 compliant?
?- compliant(tx_001).
false. % Because triggers_enhanced_check AND triggers_aml_check
LLM explains: “This transaction is flagged for 2 reasons: 1) Amount ($500k) exceeds AML threshold ($250k), 2) Belarus is high-risk. Requires manager approval before processing.”
When to use logic engines
Use logic engines when:
- You need deterministic correctness (compliance, safety)
- Rules are well-defined (policies, regulations, contracts)
- There are hard constraints (budgets, deadlines)
- You can’t afford to guess
Don’t use them when:
- The problem is inherently ambiguous
- Rules are vague or frequently change
- You need to learn from data
Building your first logic engine
Step 1: Write facts (your data)
% Your data
customer(alice, verified).
customer(bob, unverified).
product(pro, 100).
product(free, 0).
purchase(alice, pro, 5). % Alice bought Pro 5 times
Step 2: Write rules (your logic)
% Your rules
is_power_user(Customer) :- purchase(Customer, _, Times), Times > 3.
can_upgrade(Customer) :-
customer(Customer, verified),
is_power_user(Customer).
Step 3: Query (ask questions)
?- can_upgrade(alice).
true.
?- can_upgrade(bob).
false.
Tools
Prolog: SWI-Prolog (free, open source)
apt-get install swi-prolog
swipl
?- consult('rules.pl'). % Load your rules
Datalog: Souffle (fast, used in security tools)
brew install souffle
souffle -o program rules.dl
Constraint solvers: OR-Tools (free, works with Python)
pip install ortools
Common mistakes
Mistake 1: Treating logic engines as smart
They’re not. They just follow rules you write.
Bad logic: "If the customer is verified AND has spent > $1k, approve them"
Problem: What if they just spent that today and got verified yesterday?
Good logic: "IF verified 30+ days ago AND spent $1k before verification THEN approve"
Mistake 2: Not handling incomplete information
Bad rule: has_required_approvals(Loan) :- loan_approved_by(Loan, Manager1), loan_approved_by(Loan, Manager2).
Problem: If you only have 1 approval, this returns false (good), but the system crashes if the data is incomplete
Good rule:
count_approvals(Loan, Count) :- Count = count(Manager | loan_approved_by(Loan, Manager)).
has_required_approvals(Loan) :-
requires_two_approvals(Loan),
count_approvals(Loan, Count),
Count >= 2.
Mistake 3: Over-specifying rules
Start simple, add complexity as needed.
Bad:
rule(Tx) :- transaction_amount > 1000,
customer_aml_verified,
NOT in_high_risk_country,
NOT on_sanctions_list,
NOT has_recent_fraud_claim,
account_age > 30,
previous_transactions > 5,
... (20 more conditions)
Good:
high_risk(Tx) :- transaction_amount > 100000, in_high_risk_country.
high_risk(Tx) :- on_sanctions_list.
requires_review(Tx) :- high_risk(Tx).
requires_review(Tx) :- NOT customer_aml_verified.
The bridge to production systems
This post covered reasoning engines as a standalone tool. The next post shows how to combine them with agents and graphs in a production system:
- This post: Logic engines verify correctness
- Next post: Integrate with agents, graphs, LLMs in production
- Final post: Case studies and what comes next
The takeaway
Logic engines aren’t replacements for LLMs. They’re complements. Use them for:
- Verification: “Does this decision comply with all rules?”
- Constraint solving: “Find the best assignment given constraints”
- Explainability: “Here’s the exact rule that led to this decision”
LLMs are great at reasoning. Logic engines are great at verification. Together, they’re stronger than either alone.
Next in this series: Neuro-Symbolic Agents in Production: Constraint Solvers, Rule Engines, and LLM Planners