Blog › ICP guides
Ciao Prolog developer on retainer: assertion language, CLP(R) constraint programming, check postconditions, ground variable discipline, module system, and Ciao Prolog on monthly retainer
November 28, 2026 · ~15 min read
A Ciao Prolog program using CLP(R) — constraint logic programming over the reals — included a predicate check_bounds(X, Low, High, Valid) that used Ciao’s assertion language to verify that a numeric computation stayed within bounds. The assertion declared a :- check postcondition: :- check pred check_bounds(X, Low, High, Valid) : (number(X), number(Low), number(High)) => (Valid = true ; Valid = false). Inside the clause body, the developer used an inline CLP(R) constraint { X >= Low, X =< High } and assigned Valid = true in the branch where the constraint succeeded and Valid = false in the branch where it failed. On the success branch, Valid was unified immediately and the postcondition was satisfied straightforwardly. The complexity arose on a second branch that handled backtracking: the predicate was called with Valid unbound and the CLP(R) constraint was posted to the constraint store but not yet fully resolved. In this branch, the clause left Valid unbound — the constraint was present in the store, but Valid had not yet been unified to true or false because the constraint had not been decided.
Ciao’s run-time assertion checker evaluated the :- check postcondition lazily on the first solution: the postcondition Valid = true ; Valid = false was checked against the state of Valid at the point of first solution. On the backtracking branch, Valid was still an uninstantiated logical variable. The assertion checker evaluated Valid = true — a unification attempt against an unbound Valid — and this unification succeeded, unifying Valid with true. But the intent was to check that Valid was already true or false, not to bind it. The silent unification meant that 3 assertion failures per test cycle were never caught: the postcondition check passed because unification against an unbound variable always succeeds in Prolog. The Ciao developer on retainer restructured the predicate to ensure that Valid was ground before the assertion postcondition was evaluated, using explicit (Valid == true ; Valid == false) structural equality checks — not unification = — in the postcondition, and adding ground(Valid) verification before the assertion check position. The restructuring eliminated the silent false-pass: == does not bind, so Valid == true fails when Valid is unbound rather than silently succeeding; and ground(Valid) fails explicitly when Valid is still a constraint-store variable rather than a fully instantiated term. Assertion failures that should have been caught: 3/cycle → 0.
The work log read “fixed assertion checker false pass, 8h.” It names the category. It cannot explain why :- check postcondition Valid = true ; Valid = false passes when Valid is unbound — Ciao’s :- check postcondition is evaluated as a Prolog goal, and the unification operator = succeeds by binding Valid to true when Valid is uninstantiated; this means the postcondition “check” is semantically a write, not a read, for unbound variables. It cannot explain why == (structural equality, no unification) is the correct operator for postcondition checks where the intent is to verify that a variable is already ground and equal to a specific value: Valid == true fails if Valid is unbound because == does not bind, rather than silently succeeding by binding. It cannot explain why ground(Valid) is the right precondition to place before assertion evaluation when the predicate has branches that may leave output variables in the constraint store rather than ground-unified — a variable can be constrained (present in the CLP(R) store) without being ground, and an assertion postcondition that evaluates before grounding will see a constraint-store variable, not an instantiated term. The 8 hours of Ciao assertion language semantics analysis, operator selection (= vs == vs ground/1), and postcondition restructuring are invisible in the diff.
Ciao Prolog assertion language: check, trust, decl, pred assertions, and CLP(R) constraint programming
The Ciao assertion language is a formal specification layer built on top of Prolog. Assertions are directives placed in source files and are processed by both the run-time assertion checker and the CiaoPP abstract interpreter. The primary assertion form is :- check pred P(X) : Pre => Post: the check mode means the assertion is runtime-checked; Pre is the precondition evaluated on call (the types and modes of input arguments at the moment the predicate is called); Post is the postcondition evaluated on success (the types and modes of output arguments at the moment the predicate succeeds). The postcondition is evaluated as a Prolog goal at runtime, which is the source of the = vs == hazard: any Prolog goal that binds variables can appear in Post, and the = operator in a postcondition will bind rather than check uninstantiated variables.
The :- trust pred P(X) : Pre => Post form marks an assertion as trusted — it is used by the CiaoPP abstract interpreter for static analysis but is not checked at runtime. Trust assertions are how developers provide the static analyzer with information about predicates whose correctness cannot be derived automatically: foreign predicates, built-in predicates not known to the analyzer, and predicates whose behavior is assumed correct for the purposes of a particular analysis. The :- decl form provides compile-time assertions about program declarations, and :- true pred serves as documentation that is not checked. The key operational distinction for retainer work: check assertions consume runtime overhead but catch bugs during testing; trust assertions are zero runtime cost but enable CiaoPP to reason interprocedurally about the program without false positives from under-approximation.
CLP(R) — constraint logic programming over the reals — is activated in Ciao via use_module(library(clpr)). Constraints are posted using the { } syntax: { X + Y =:= Z } posts the linear arithmetic constraint to the constraint store. Predicates can succeed with variables still present in the constraint store: a predicate that posts { X >= 0.0 } succeeds immediately, but X remains a constrained variable rather than a ground number. The entailed(C) built-in checks whether a constraint C is entailed by the current constraint store without posting it — this is the correct tool for verifying a constraint property without extending the store, and is the right pattern for assertion postconditions that need to check constraint properties without side effects. The dump(Vars, Constraints) predicate extracts the current constraints on a list of variables as a list of terms, useful for debugging constraint store state.
CLP(FD) for finite domains is activated via use_module(library(clpfd)). Domain declarations use ins/2: X ins 1..10 constrains X to the integer domain 1 through 10. The label/1 predicate enumerates solutions by successively binding domain variables to values in their domains via backtracking — after label/1, all labeled variables are ground. The all_distinct/1 constraint enforces that all elements of a list take distinct values, the standard pattern for constraint satisfaction problems like N-queens or graph coloring. The distinction between CLP(R) and CLP(FD) retainer work: CLP(R) constraint store variables are frequently left unground through most of a computation and grounded only at the end, making the ground/1 discipline in assertion postconditions particularly critical; CLP(FD) variables are typically grounded by explicit label/1 calls, so the assertion postcondition grounding issue is less common but the constraint propagation architecture is more complex.
Ciao’s module system is activated by the :- module(Name, Exports, Packages) declaration at the top of a source file. The third argument is a list of packages — compiler extensions that modify the syntax and semantics of the module. The assertions package activates assertion processing; without it, assertion directives are ignored. Other packages include dcg for definite clause grammar syntax, hiord for higher-order programming support, and regtypes for regular type declarations. The package system is what makes Ciao extensible without modifying the base language: Ciao’s base language is a minimal Prolog, and all extended features are loaded via packages. The use_module directive imports predicates from modules; the use_package directive (or the third argument of the module declaration) activates compiler-level extensions.
Ciao module system, DCG grammar rules, and higher-order call/N
Ciao’s module system enforces strict namespace discipline. Each module declares its name, the list of predicates it exports, and the packages it uses. Importing predicates from another module requires an explicit use_module directive; predicates not listed in the module’s export list are not accessible from outside the module. This makes Ciao module system design a retainer engineering activity distinct from SWI-Prolog’s more permissive module system: in Ciao, incomplete export lists produce visible errors rather than silent failures, and the interprocedural analysis in CiaoPP depends on accurate export specifications to reason about predicate call graphs across module boundaries. Module export list design — deciding which predicates to expose, which to keep internal, and how to structure the public API of a constraint-solving module — is a significant fraction of Ciao retainer advisory work for teams building library-style constraint modules.
DCG grammar rules in Ciao use the standard --> notation. A grammar rule sentence --> noun_phrase, verb_phrase is compiled to a Prolog predicate sentence(S0, S) where S0 is the input difference list and S is the remaining list after parsing. The phrase/2 and phrase/3 predicates call DCG rules directly: phrase(sentence, Tokens) attempts to parse the entire token list as a sentence; phrase(noun_phrase, Tokens, Rest) parses a noun phrase and unifies Rest with the remaining tokens. Pushback notation e --> [a], e, [b], remainder allows a grammar rule to push tokens back onto the remaining input, enabling lookahead without consuming tokens. DCG engineering in Ciao is activated by the dcg package and is a common pattern for natural language processing, protocol parsing, and term transformation pipelines in Ciao-based research systems.
Higher-order programming in Ciao uses call/N: call(Goal, Arg1, ..., ArgN) calls Goal with additional arguments appended. The maplist/2 and maplist/3 predicates from Ciao’s standard library apply a goal to each element of a list: maplist(number, Xs) checks that all elements of Xs are numbers; maplist(succ, Xs, Ys) computes the successor of each element. Higher-order programming requires the hiord package in Ciao for full higher-order support including lambda expressions and higher-order type specifications. Ciao was developed by Manuel Hermenegildo and colleagues at the Technical University of Madrid (UPM) and is one of the few Prolog systems that includes both a formal assertion language and an abstract interpretation-based static analyzer — CiaoPP — that can verify assertions statically before runtime. Its closest retainer-ecosystem relatives are SWI-Prolog for general-purpose Prolog retainers and GNU Prolog for constraint programming, but Ciao’s assertion language, CiaoPP static analysis integration, and CLP(R)/CLP(FD) constraint programming discipline make the retainer work distinct in formal specification enforcement and constraint domain analysis.
How HourTab tracks Ciao Prolog developer retainer hours
Ciao Prolog retainer work carries the invisible-work problem of all formal specification language retainers, amplified by the gap between the brevity of a one-line assertion directive and the depth of semantics required to write it correctly. Logic programming teams using Ciao for constraint satisfaction problems frequently encounter the = vs == postcondition hazard when a developer adds assertion annotations to an existing CLP(R) predicate: the natural instinct is to write Valid = true ; Valid = false in the postcondition, because that is how values are assigned in Prolog clause bodies; but in an assertion postcondition evaluated as a goal, = is unification, not equality check, and unification against an unbound variable always succeeds by binding. The three false-passing assertions per cycle described above are three instances of the postcondition silently binding Valid rather than verifying it; the retainer work is the assertion language semantics analysis that identifies the = vs == vs ground/1 distinction and the postcondition restructuring that enforces grounding before evaluation.
HourTab gives Ciao Prolog developers a public retainer-hours URL they send to clients — typically logic programming teams using Ciao for constraint satisfaction problems, research groups using CiaoPP for static verification of Prolog programs, and Prolog shops migrating to Ciao’s typed and asserted Prolog for more reliable production systems. For Ciao retainers, each work log entry should name the mechanism (assertion postcondition = to == operator correction; ground/1 verification addition before assertion evaluation; CLP(R) constraint store grounding analysis; entailed/1 check addition; use_package(assertions) integration; trust pred annotation for CiaoPP; CLP(FD) all_distinct constraint design; DCG grammar rule engineering), the specific predicate names, assertion types, constraint domains, and operator selections involved in the bug, and the before/after metric. Ciao retainers are often compared to SWI-Prolog developer retainers for the shared Prolog-family positioning, but Ciao’s assertion language, CiaoPP abstract interpretation, module and package architecture, and CLP(R)/CLP(FD) grounding discipline make the retainer work distinct in formal specification semantics and constraint domain analysis. HourTab’s work log makes the operator selection analysis, grounding verification design, and constraint store state reasoning visible to clients who would otherwise see only the outcome — assertion failures that should be caught: 3/cycle → 0 — and not understand why the fix required understanding the difference between Prolog unification and structural equality, and why a one-character change from = to == in an assertion postcondition represents 8 hours of language semantics analysis.
Track Ciao Prolog developer retainer hours without the status emails
HourTab gives Ciao Prolog developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your assertion engineering log becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: Ciao Prolog developer retainers
What does a Ciao Prolog developer on retainer typically do?
A Ciao Prolog developer on monthly retainer covers Ciao assertion language engineering (check/trust/decl/true pred assertions, = vs == vs ground/1 in postconditions, runtime vs static check modes), CLP(R) and CLP(FD) constraint programming (constraint store management, entailed/1, label/1, all_distinct), Ciao module and package system (use_module, use_package, assertion package activation), DCG grammar rule design, and higher-order call/N programming (maplist/2, maplist/3, foldl patterns, partial application via call/N).
What Ciao Prolog work is most commonly underlogged in a retainer?
Assertion postcondition operator selection (= vs == vs ground/1; unification in postcondition silently binds unground variables; 3 false-passing assertions/cycle → 0; 8–14 hrs invisible); CLP(R) constraint store grounding (variable in constraint store but not ground at assertion check time; entailed/1 check addition; 6–11 hrs invisible in constraint store state analysis and grounding discipline); CiaoPP abstract interpretation integration (trust pred annotations for analyzer; module export spec for interprocedural analysis; 7–13 hrs invisible in static analysis configuration and annotation engineering).
What are typical Ciao Prolog developer retainer rates?
Entry-level Ciao Prolog developers (1–2 years, Prolog basics, basic CLP(R), basic assertion syntax) bill at $65–$115/hr. Mid-level Ciao Prolog engineers (2–4 years, assertion language semantics, = vs == discipline, CLP(R)/CLP(FD), CiaoPP integration) bill at $110–$185/hr. Senior Ciao Prolog architects (4–8 years, CiaoPP abstract interpretation, Ciao module/package architecture, complex constraint programming, DCG grammar engineering) bill at $155–$275/hr. Monthly retainer ranges: $1,800–$4,400/mo advisory (15–25 hrs), $6,000–$16,000/mo full development.
What should a Ciao Prolog developer retainer agreement include?
A Ciao Prolog developer retainer agreement should specify: assertion language scope (check/trust/decl pred assertions, = vs == operator selection, ground/1 verification, runtime vs static check modes); CLP scope (CLP(R), CLP(FD), constraint store, entailed/1, label/1, all_distinct); module/package scope (use_module, use_package, assertion package); DCG scope; CiaoPP scope (trust pred annotations, abstract interpretation integration); and hour logging format (advisory category, before/after false-passing assertion or wrong constraint value metric, Ciao version, whether fix required == operator, ground/1, entailed/1, or trust pred annotation).
How should Ciao Prolog developer retainer hours be logged?
Log each Ciao Prolog retainer session with: advisory category (assertion postcondition = to == operator correction; ground/1 verification addition before assertion evaluation; CLP(R) constraint store grounding analysis; entailed/1 check addition; use_package(assertions) integration; trust pred annotation for CiaoPP; CLP(FD) all_distinct constraint design; DCG grammar rule engineering); the specific predicate names, assertion types, constraint domains, and operator selections involved in the bug (check pred check_bounds/4 postcondition Valid = true ; Valid = false passed when Valid unbound because = unifies; restructured to ground(Valid), Valid == true ; Valid == false; false-passing assertions: 3/cycle → 0); and the before/after metric. Include Ciao version and whether fix required == operator, ground/1, entailed/1, or trust pred annotation.