Blog › ICP guides

Pop-11 developer on retainer: pattern matching database, variable binding, production rules, OBJECTCLASS, and Poplog platform engineering on monthly retainer

October 31, 2026 · ~18 min read

A Pop-11 AI customer classification system was producing four silent pattern match failures per day. The system used Pop-11’s database working memory with pattern matching to classify customer records: a set of assertions in the database described customers, and a pattern match procedure checked whether each customer matched a “premium” classification pattern. The pattern template was [?customer = premium] — intended to bind the variable customer to the first element and test whether the second element equaled the word premium. A global variable named customer existed in the module scope, holding the customer ID from the last processed record. On days when the prior customer ID happened to not match the pattern, Pop-11’s pattern matcher resolved ?customer to the existing global value and tested equality against it instead of performing a fresh binding — causing the match to silently return false when it should have bound and succeeded. The Pop-11 developer on retainer diagnosed the root cause: in Pop-11 pattern matching, ?X binds to an existing variable X if X already has a value in scope, testing equality rather than performing a fresh binding. The fix used the ! prefix form !customer for fresh local binding in the pattern template, and added an explicit lvars customer; declaration before the match to give the pattern variable its own local scope. Failed matches: 4 per day → 0.

The work log entry read “fixed customer classification pattern match bug, 13h.” It names the symptom and the duration. It cannot explain to a client why ?customer in a Pop-11 pattern template behaves differently depending on whether a variable named customer exists in scope at the time of matching (if it does, the match tests equality against the existing value; if it doesn’t, the match creates a fresh binding — making the behavior of ?-prefixed pattern variables dependent on global scope state), why the failure was silent (the matcher returned false for non-matching attempts, which is correct behavior for an equality test but wrong behavior for the intended binding), why ! (the exclamation-mark prefix) forces a fresh local binding regardless of whether a variable of the same name exists in scope, why lvars vs vars vs dlocal create different variable scoping semantics in Pop-11, or why the pattern template [?x = y ^^rest] uses three different prefix forms in one expression and what each does. The 13 hours of pattern match template audit (identifying all templates where variable names could collide with globals), variable scope analysis (determining which variables were in scope at each match call site), global name collision identification (finding all cases where the module had globals with names used in pattern templates), and refactoring verification (confirming that fresh bindings work correctly after the lvars and ! fix) are not visible in the diff beyond restructured pattern templates and added variable declarations.

Pop-11’s pattern matching system: binding forms, the database, and assertion management

Pop-11 is a procedural AI programming language from the University of Edinburgh, developed in the 1970s as part of the POPLOG system (which also integrates Prolog, Common Lisp, and Standard ML in a single runtime). Pop-11’s most distinctive features are its database working memory and pattern matching system, designed specifically for AI production rule systems and knowledge representation. The Pop-11 database is a global list of assertions, each of which is a Pop-11 list (an arbitrary Lisp-like list structure). The database is manipulated through a set of built-in procedures: add([assertion]) adds an assertion to the database, remove([pattern]) removes the first matching assertion, present([pattern]) returns true if any database assertion matches the pattern, flush([pattern]) removes all database assertions matching the pattern, allpresent([[p1][p2]...]) tests whether all patterns match simultaneously (conjunction), and foreach [pattern] do ... endforeach iterates over all matching database assertions.

Pop-11’s pattern matching uses a template list syntax with three prefix forms for pattern elements. =X (equals sign prefix) matches the corresponding list element only if it equals the current value of the variable X — it is a pure equality test, not a binding. ?X (question mark prefix) attempts to bind: if X is a local variable without a value, it binds to the matching element; if X already has a value (either as a local variable with a value or as a global variable), it tests equality against the existing value. ! (exclamation mark prefix, !X) forces a fresh local binding regardless of any existing value of X in scope — it always creates a new binding. ^^X (double-caret prefix) matches a list segment (zero or more consecutive elements) and binds them as a list. The pattern [hello ?name = teacher ^^courses] matches a list starting with the word hello, binds the second element to name (or tests equality if name has a value), tests that the third element equals the word teacher, and binds all remaining elements as a list to courses. The == pattern element (double equals, no prefix) matches any single element. [X ==] as a tail match is a common idiom: the == at the tail matches any remaining elements, allowing the pattern to test only the beginning of a list.

Pop-11’s variable binding model is the source of the most common retainer pattern matching bugs. lvars declares a lexically scoped local variable that is visible only within the current procedure body and does not inherit values from outer scopes. vars declares a dynamically scoped global variable that is visible throughout the program and whose value persists across procedure calls (unless shadowed by a dlocal binding). dlocal declares a dynamic variable binding that saves the current value of a global variable on entry and restores it on exit: dlocal customer; inside a procedure saves customer’s value on entry and restores it when the procedure exits, even via exceptions. Pattern matching uses the scope that is active at the match call site. If a vars global named customer has a value when the pattern matcher encounters ?customer, the matcher uses the existing value for an equality test. If customer is declared as lvars customer; in the same scope and is currently uninitialized (holding the Pop-11 uninitialized sentinel undef), the matcher performs a fresh binding. The correct pattern for fresh bindings in match templates is always: declare lvars customer; before the match (or use !customer in the template), ensuring the pattern variable has local scope with no inherited value.

The Pop-11 database operations in detail: add([customer alice premium]) adds the assertion [customer alice premium] to the database. remove([customer ?name ==]) removes the first database entry that matches the pattern (starts with customer, binds the second element to name, and has any remaining elements). present([customer alice ==]) returns true if any database entry starts with customer and alice. flush([customer ==]) removes all database entries starting with customer. allpresent([[customer alice ==] [status == premium]]) tests that both patterns match (with consistent bindings across patterns). foreach [customer ?name ==] do process(name) endforeach iterates over all database entries matching the pattern and processes each. The database is an unordered set of lists; order within pattern templates matters for structure matching, but order of database entries does not matter for present (which finds any match). Managing the database lifecycle correctly — flushing stale assertions between problem-solving episodes, using allpresent to check complex preconditions before firing production rules, and using remove carefully to delete only the correct assertion when multiple entries match the same broad pattern — is central to Pop-11 AI application retainer work.

Pop-11 procedure definitions, list operations, and higher-order programming

Pop-11 procedure definitions use define name(args); body enddefine; syntax. Procedures are first-class values: they can be stored in variables, passed as arguments, and returned from other procedures. The stack-based value model means that procedures can return multiple values without explicit tuple packing: a procedure that executes 3; 4; 5; on the Pop-11 stack returns three values to its caller. Pop-11 uses postfix assignment: value -> variable assigns the value to the variable (the arrow points into the variable). Double arrow --> is used for pattern matching assignment: [alice premium] --> [?name =premium] matches the pattern against the list and assigns to name. The -> assignment works for all variables including record fields: value -> record.field sets a field.

Pop-11’s list operations: <> (angle-bracket concatenation) concatenates two lists: [a b] <> [c d] produces [a b c d]. << appends an element to the front of a list: x << list prepends x. hd(list) returns the first element; tl(list) returns all but the first element; last(list) returns the final element; rev(list) reverses the list. Pop-11 lists are singly linked: hd and tl are O(1) but last is O(n). List construction uses the [...] bracket syntax: [a b c] creates a list with three elements. List interpolation uses ^variable to splice in a value and ^^list to splice in a list: [hello ^name ^^rest] creates a list starting with hello, then the value of name, then all elements of rest. Higher-order procedures: maplist(list, proc) applies proc to each element and returns a list of results; applist(list, proc) applies proc to each element for side effects; filter(list, pred) returns elements satisfying pred; foldl(list, init, proc) left-folds with initial value.

Pop-11’s section system provides namespace management for large programs. section name => exports; code endsection creates a named section where identifiers are private to the section unless listed in the => exports clause. Sections prevent global name collisions between different modules, which is particularly important in Pop-11 because global vars declarations are truly global and visible everywhere unless section boundaries restrict access. A common retainer task is restructuring a monolithic Pop-11 AI system into sections that export only their public interfaces, eliminating the global name collisions that cause pattern match binding bugs (like the ?customer problem) and making the namespace structure explicit. section declarations do not prevent dynamic binding from crossing section boundaries — dlocal variables can still be accessed across sections — but they do prevent static name resolution from importing global vars names across section boundaries without explicit export.

Pop-11 production rule systems, OBJECTCLASS, and the Poplog multi-language platform

Pop-11’s database and pattern matching system is the substrate for production rule AI systems. A forward-chaining production rule has two parts: a condition (a pattern that must match the database) and an action (database modifications and other effects). A simple production rule system in Pop-11: a list of rules where each rule is a pair [condition action]; a rule fires when its condition pattern matches an assertion in the database; firing executes the action. The forward-chaining inference loop: repeatedly scan the rule list, find the first rule whose condition matches the current database, fire that rule (execute its action, which typically adds or removes database assertions), and repeat until no rules match. Conflict resolution — choosing among multiple applicable rules — is typically done by rule ordering (declaration order, specificity order, or recency) and is the primary source of production rule system bugs in retainer work: the wrong rule fires when multiple rules match, producing an incorrect inference path. The retainer fix designs an explicit conflict resolution strategy: rule specificity (rules with more conditions are more specific and preferred), recency (rules that match more recently added assertions are preferred), or refractoriness (a rule cannot fire again for the same database state it matched before).

Backward-chaining in Pop-11: instead of firing rules to add facts, backward-chaining starts from a goal assertion and decompose it into subgoals that must be proved. A backward-chaining system defines rules as [goal conditions...]: to prove goal, prove each condition in conditions. The inference engine tries to prove a top-level goal by matching it against rule heads, then recursively proving the conditions of matching rules. The Pop-11 Prolog integration (via Poplog’s shared runtime) allows backward-chaining logic to be written in Prolog and called from Pop-11: prolog_eval([prove, Goal]) evaluates a Prolog goal from Pop-11 code, with results accessible as database assertions. Mixed Pop-11/Prolog programs combine Pop-11’s procedural AI (database manipulation, production rules, imperative control flow) with Prolog’s logic programming (Horn clauses, unification, backtracking) in a single Poplog session.

OBJECTCLASS is Pop-11’s object-oriented extension, providing class-based OOP on top of the core procedural language. define :class CUSTOMER; slots: customer_id, name, tier; enddefine declares a class with three slot attributes. Each slot generates accessor procedures: customer_id(obj) reads the customer_id slot; customer_id(obj) -> value sets it (using Pop-11’s postfix assignment). new_customer(id, name, tier) creates a new CUSTOMER instance. Class methods are defined with define :method process(obj: CUSTOMER); body enddefine — Pop-11 dispatches process(obj) calls to the most specific method based on the runtime type of obj. Method inheritance: subclasses defined with define :class PREMIUM_CUSTOMER is CUSTOMER; inherit all slots and methods from CUSTOMER; subclass methods can call call_next_method() to invoke the superclass implementation. OBJECTCLASS also supports metaclasses, mixins, and before/after method wrappers for aspect-oriented patterns. The retainer work around OBJECTCLASS is designing class hierarchies that correctly express the type relationships in the domain, ensuring that slots are declared at the right level of the hierarchy (on the class whose instances should have them, not always at the root), and designing method override patterns that correctly use call_next_method rather than reimplementing superclass behavior.

The Poplog runtime integrates four languages in a single persistent image: Pop-11, Prolog, Common Lisp, and Standard ML. Each language’s procedures and data structures are directly accessible from the others. A Poplog session maintains a single global environment where Pop-11 variables can hold Prolog terms, Common Lisp symbols, and SML values. Pop-11 is the extension language for VED (the Virtual EDitor — Poplog’s built-in editor), which allows Pop-11 procedures to be defined as editor commands, keybindings, and markup syntax extensions. VED macro authorship in Pop-11: define ved_mycommand(); ved_insert_string('hello'); enddefine defines a VED command invoked with ENTER mycommand. The retainer work around Poplog platform engineering is designing the language boundary points (when to use Pop-11 vs Prolog vs Common Lisp for each component), managing the shared runtime state (Pop-11 global variables accessible from Prolog, Common Lisp top-level variables accessible from Pop-11), and authoring VED extensions for AI-assisted editing workflows.

How HourTab tracks Pop-11 developer retainer hours

Pop-11 retainer work shares the invisible-work problem with all AI programming language retainers, with the additional challenge that Pop-11’s most important retainer tasks — pattern match template audit for variable binding vs equality collision, database lifecycle management for correct assertion scoping across AI episodes, production rule conflict resolution strategy design, and Poplog multi-language boundary design — produce diffs whose surface area is small relative to the analytical work required. Adding ! prefix to pattern variables and lvars declarations before match calls is a diff with changes to a dozen pattern templates; the value is correct fresh binding for all pattern variables regardless of global scope state, elimination of silent match failures on days when globals hold values from prior processing, and pattern code that correctly expresses binding intent rather than accidental equality testing. Adding flush calls at AI episode boundaries is a diff with three lines; the value is correct database state isolation across problem-solving episodes, elimination of cross-episode assertion contamination, and AI inference results that reflect only the current episode’s facts. Designing a specificity-based conflict resolution strategy for a production rule system is a diff across the rule firing loop and rule priority comparison; the value is correct rule selection (most specific applicable rule fires first), elimination of general rules incorrectly overriding specific rules, and inference paths that follow the intended reasoning structure.

HourTab gives Pop-11 developers a public retainer-hours URL they send to clients — typically AI research laboratories using Poplog as a platform for cognitive architecture research, universities using Pop-11 for AI programming courses, and robotics research teams using Pop-11’s production rule systems for behavior architecture — at the start of an engagement. For Pop-11 retainers, each work log entry should name the mechanism (! fresh binding prefix vs = equality test vs ? variable binding analysis; ^^X list segment expansion design; database pattern template audit for variable name collisions; lvars/vars/dlocal variable scope design; pattern match template restructuring for collision-safe binding; add/remove/present/foreach/flush/allpresent database operation design; database lifecycle management design; assertion store population and cleanup; allpresent multi-pattern conjunction; forward-chaining rule condition/action design; backward-chaining goal decomposition; conflict resolution strategy design; OBJECTCLASS class hierarchy design with slot declarations; accessor procedure design; call_next_method superclass delegation; section/endsection namespace design; dlocal dynamic variable binding; VED editor macro extension; Poplog Prolog/prolog_eval integration), the specific pattern template and the variable collision or database contamination problem, and the before/after observable metric. Pop-11 retainers are often compared to Prolog developer retainers for logic programming and pattern matching work, and to Smalltalk developer retainers for multi-paradigm OOP platform engineering. The distinction from Prolog is the Pop-11 database model: Pop-11’s database is an imperative mutable working memory (add/remove/flush) rather than Prolog’s declarative clause store (assert/retract), and Pop-11’s pattern matching is list-based equality rather than unification-based term matching. HourTab’s work log makes the pattern match template audit and database lifecycle design visible to clients who would otherwise see only the symptom — silent match failures with no error messages — and not understand why the fix required understanding Pop-11’s variable binding semantics and the ? vs ! prefix distinction.

Track Pop-11 developer retainer hours without the status emails

HourTab gives Pop-11 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 work log becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: Pop-11 developer retainers

What does a Pop-11 developer on retainer typically do?

A Pop-11 developer on monthly retainer covers four principal service areas: pattern matching and database design (! fresh binding prefix vs = equality test vs ? variable binding analysis; ^^X list segment expansion; database add/remove/present/foreach/flush/allpresent assertion store design; lvars/vars/dlocal variable scope design; pattern template audit for variable name collisions); production rule system design (forward-chaining rule condition/action design; backward-chaining goal decomposition; conflict resolution strategy design; rule compilation pipeline; rule firing trace instrumentation); OBJECTCLASS OOP design (class hierarchy with slot declarations; accessor procedure design; class method authorship; call_next_method superclass delegation; metaclass extension design; mixed paradigm integration); and Poplog platform engineering (Pop-11/Prolog integration via prolog_eval; Common Lisp integration; section/endsection namespace management; dlocal dynamic binding; VED editor macro extension).

What Pop-11 work is most commonly underlogged in a retainer?

Pattern match variable binding collision repair (?customer in pattern template resolving to existing global rather than creating fresh binding; match silently returning false instead of binding; added ! prefix and lvars customer; declaration; failed matches: 4/day → 0; 13–22 hrs invisible in template audit, scope analysis, and collision identification), database assertion management design (no flush between AI problem-solving episodes; assertions from prior episodes contaminating later episodes; episode contamination: 3/run → 0 after flush at episode boundaries and allpresent precondition checks; 10–18 hrs invisible in database lifecycle design and contamination trace analysis), and production rule conflict resolution design (rules firing in declaration order rather than specificity; general rule overriding specific applicable rule in 5 steps/session; designed specificity-based conflict resolution; wrong rule selections: 5/session → 0; 11–19 hrs invisible in rule specificity analysis and conflict resolution strategy design).

What are typical Pop-11 developer retainer rates?

Entry-level Pop-11 developers (1–2 years, define/enddefine procedure syntax, basic add/remove/present database operations, simple ?X pattern binding, foreach iteration) bill at $55–$100/hr. Mid-level Pop-11 engineers (2–4 years, ! vs ? vs = binding distinction, ^^X list segment expansion, lvars/vars/dlocal scope design, production rule system authorship, OBJECTCLASS hierarchy design, Poplog multi-language integration) bill at $100–$175/hr. Senior Pop-11 architects (4–8 years, full Pop-11 AI application architecture with complex production rule inference engines, OBJECTCLASS metaclass systems, Poplog Prolog/Common Lisp/SML integration, VED macro extension development, Pop-11 compiler extension work) bill at $150–$270/hr. Monthly retainer ranges: $2,000–$4,000/mo advisory (15–25 hrs), $6,000–$14,000/mo for full Pop-11 AI platform engagements.

What should a Pop-11 developer retainer agreement include?

A Pop-11 developer retainer agreement should specify: pattern matching scope (! fresh binding prefix vs = equality test vs ? variable binding analysis; ^^X list segment expansion; database pattern template audit for variable name collisions; lvars/vars/dlocal scope design; template restructuring); database scope (add/remove/present/foreach/flush/allpresent design; database lifecycle management; assertion store population and cleanup; episode boundary design; allpresent conjunction); production rule scope (forward-chaining rule design; backward-chaining goal decomposition; conflict resolution strategy; rule compilation pipeline; rule firing trace); OBJECTCLASS scope (class hierarchy design; slot declaration; accessor procedure; class method; call_next_method; metaclass extension; mixed paradigm integration); Poplog integration scope (Pop-11/Prolog integration; Common Lisp integration; section/endsection namespace; dlocal dynamic binding; VED macro extension); and hour logging format (pattern template variable name; database operation type; rule system type; episode count; Poplog version).

How should Pop-11 developer retainer hours be logged?

Log each Pop-11 retainer session with: advisory category (! fresh binding prefix vs = equality test vs ? variable binding analysis; ^^X list segment expansion design; database pattern template audit for variable name collisions; lvars/vars/dlocal scope design; pattern match template restructuring; add/remove/present/foreach/flush/allpresent database operation design; database lifecycle management; forward-chaining rule condition/action design; backward-chaining goal decomposition; conflict resolution strategy design; rule firing trace instrumentation; OBJECTCLASS class hierarchy design; slot declaration; accessor procedure design; call_next_method superclass delegation; section/endsection namespace; dlocal dynamic binding; VED editor macro extension; Poplog Prolog/prolog_eval integration), the specific pattern template and variable collision problem (?customer in template resolving to existing global; added ! prefix and lvars customer;; failed matches: 4/day → 0), and the before/after metric (silent pattern match failures/day: 4 → 0; episode contamination errors/run: 3 → 0; wrong rule selections/session: 5 → 0). Include Poplog version, OS, and whether the fix required binding prefix restructuring, database lifecycle redesign, conflict resolution strategy design, or OBJECTCLASS hierarchy authorship.