Why Constraint Solvers Run Alongside LLMs
LLMs understand context. Constraint solvers enforce constraints. You need both, and they must not be confused.
The Problem
LLMs are good at understanding context and generating solutions that sound reasonable. "Reasonable" is not the same as "feasible."
Resource constraints, scheduling conflicts, capacity limits, regulatory boundaries — these are not approximation problems. They are satisfaction problems. A commitment that violates a hard constraint is not a commitment; it is a promise that cannot be kept. And a promise that cannot be kept is worse than no promise at all, because it consumes resources, creates dependencies, and damages trust before the failure becomes visible.
LLMs hallucinate feasible-sounding but infeasible solutions. Not because they are defective — because they are probabilistic. They generate outputs that are statistically consistent with their training, not outputs that are logically consistent with the constraints of your specific resource allocation problem. Constraint solvers do not hallucinate. They either find a solution that satisfies all constraints, or they prove that no such solution exists and return an infeasibility report.
The architectural question is not "LLM or constraint solver?" The question is "which tool for which step?" LLMs understand context and generate problem formulations. Constraint solvers find exact solutions within those formulations. Using only one of them produces a system that is either brittle (constraint solver without context) or unreliable (LLM without hard constraint enforcement).
Current Options
Why OR-Tools / Constraint Programming
A constraint solver either finds a solution that satisfies all constraints or proves that no such solution exists. There is no "probably feasible" or "seems reasonable." The answer is exact.
When no solution exists, the solver returns an infeasibility report that identifies which constraints cannot simultaneously be satisfied. This is actionable information — not a failure, but a diagnosis.
The same constraint formulation produces the same solution on every run. This is a requirement for commitment systems: a commitment formed from a resource allocation must be traceable to the specific constraint satisfaction that justified it.
A solver does not generate solutions that violate constraints and then explain them away. If a solution violates a constraint, it is not returned. The constraint is not approximate.
Trade-offs
Constraints must be formalized. This is an upfront investment — the constraint model must be designed, validated, and maintained as constraints evolve. It cannot be approximated at runtime.
A constraint solver operates on a formal problem formulation. It does not understand the organizational context that produced that formulation. The LLM layer provides this context; without it, the solver is a powerful tool without a problem to solve.
Some constraint satisfaction problems are NP-hard. Large problem spaces can make the solver slow or intractable without careful problem decomposition and modeling. Well-scoped commitment problems rarely hit these limits.
Our Motivation
In the commitment infrastructure, constraint solvers act as the feasibility gate on every commitment that involves resource allocation, scheduling, or hard regulatory limits.
The flow is: 1. The coordination layer (LLM) synthesizes context and generates a `CommitmentProposal` — a structured description of what the organization is trying to commit to, including resource requirements and dependencies. 2. The commitment layer passes the `CommitmentProposal` to the constraint solver with the current resource state. 3. The solver returns a `ConstraintVerificationResult`: either `Feasible` (with the specific allocation that satisfies all constraints) or `Infeasible` (with the infeasibility report identifying which constraints cannot be satisfied). 4. A `CommitmentEvent` can only be formed if the `ConstraintVerificationResult` is `Feasible`. An infeasible proposal cannot become a commitment by construction.
The constraint solver is not a validation step added on top of an LLM decision. It is a gate that determines whether the proposed commitment is achievable given the organization's actual resource state and constraints. The LLM understands what the organization is trying to commit to. The solver determines whether that commitment is achievable.
In Practice
from ortools.sat.python import cp_model
def verify_resource_commitment(proposal: CommitmentProposal, state: ResourceState) -> ConstraintResult:
"""
Verify that a commitment proposal is feasible given current resource state.
Returns Feasible with allocation, or Infeasible with diagnosis.
"""
model = cp_model.CpModel()
# Decision variables: how much of each resource does this commitment consume?
allocations = {
resource_id: model.new_int_var(0, capacity, f"alloc_{resource_id}")
for resource_id, capacity in state.available_capacity.items()
}
# Hard constraint: total allocation across existing commitments + this proposal
# must not exceed capacity for any resource
for resource_id, capacity in state.available_capacity.items():
existing_load = state.committed_load.get(resource_id, 0)
required = proposal.resource_requirements.get(resource_id, 0)
model.add(allocations[resource_id] == required)
model.add(existing_load + allocations[resource_id] <= capacity)
# Hard constraint: dependencies must be scheduled before this commitment
for dep_ref in proposal.dependencies:
dep_end = state.get_commitment_end_time(dep_ref)
if dep_end is None:
return ConstraintResult.infeasible(
reason=f"Dependency {dep_ref} is not yet committed or scheduled"
)
model.add(proposal.earliest_start >= dep_end)
# Solve
solver = cp_model.CpSolver()
status = solver.solve(model)
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
allocation = {rid: solver.value(var) for rid, var in allocations.items()}
return ConstraintResult.feasible(allocation=allocation)
else:
# Diagnose which constraints are causing infeasibility
return ConstraintResult.infeasible(
reason=diagnose_infeasibility(model, proposal, state)
) The constraint model is explicit and complete. Every hard constraint is encoded. The solver either returns a valid allocation or an infeasibility report. There is no middle state — no "this might work" or "try adjusting the requirements." The result is binary and exact.
/// The commitment gate: a commitment can only be formed if constraint verification
/// returns Feasible. This is enforced by the type system — CommitmentEvent requires
/// a ConstraintVerificationResult, and form_commitment checks it is Feasible.
pub enum ConstraintVerificationResult {
Feasible {
allocation: ResourceAllocation,
verified_at: DateTime<Utc>,
},
Infeasible {
report: InfeasibilityReport,
},
}
impl ConstraintVerificationResult {
pub fn is_feasible(&self) -> bool {
matches!(self, Self::Feasible { .. })
}
pub fn infeasibility_report(&self) -> Option<&InfeasibilityReport> {
match self {
Self::Infeasible { report } => Some(report),
Self::Feasible { .. } => None,
}
}
}
/// An infeasibility report: what constraints cannot be satisfied, and why.
/// Returned to the coordination layer so the next coordination cycle
/// can reformulate the proposal with different parameters.
#[derive(Debug, Serialize)]
pub struct InfeasibilityReport {
pub violated_constraints: Vec<ConstraintViolation>,
pub diagnosis: String,
pub suggested_reformulations: Vec<String>,
} The Rust type system enforces the gate. ConstraintVerificationResult is an enum — Feasible or Infeasible. form_commitment checks is_feasible() and returns Err if the result is Infeasible. There is no way to form a CommitmentEvent from an infeasible proposal without changing the type — which means changing the code and being reviewed.
Looking Forward
Constraint programming as a component in LLM-augmented systems will become standard as the cost of hallucinated feasibility becomes visible in production. The pattern is already established in operations research and logistics; what is new is the integration with LLMs as the context-understanding layer.
The tooling is maturing. OR-Tools (Google) provides a mature, production-grade constraint solver with Python and C++ bindings. The integration surface with LLM coordination layers is well-defined: the LLM produces a structured problem formulation; the solver consumes it and returns a solution or an infeasibility report.
As constraint modeling libraries mature and LLMs become better at generating valid constraint formulations, the integration overhead will decrease. The fundamental architecture — LLM for context, solver for constraints — will not change, because it reflects a real distinction in what these tools are and what they can guarantee.
Recommendation
Use constraint solvers as the feasibility gate on every commitment that involves resource allocation, scheduling, or hard regulatory limits. OR-Tools is the practical choice: mature, production-grade, Python and C++ bindings, maintained by Google.
Use LLMs to understand what the organization is trying to commit to. Use constraint solvers to determine if that commitment is achievable given the organization's actual resource state and constraints. Do not ask the LLM to verify feasibility — it will approximate. Do not ask the solver to understand organizational context — it will produce a technically correct answer to the wrong problem.
The integration pattern is: LLM produces a structured `CommitmentProposal`; solver returns `Feasible` or `Infeasible`; the commitment layer gates on this result. An infeasible proposal returns to the coordination layer with the infeasibility report as context for the next coordination cycle.