# miniKanren-on-SX: deferred work The main plan (`plans/minikanren-on-sx.md`) carries Phases 1–7 through the naive-tabling milestone. This file collects the four pieces left on the shelf, with enough scope and design notes to drive a follow-up loop. Branch convention: keep the same `loops/minikanren` worktree; commit and push to `origin/loops/minikanren`. Squash-merge to `architecture` only when each numbered piece is shipped + tests green. Cumulative test count snapshot at squash-merge: **644** across **71 test files**. Every change below should grow the number, not break existing tests. ## The four pieces ### Piece A — Phase 7 SLG (cyclic patho, mutual recursion, fixed-point iteration) **Problem.** Naive tabling drains the answer stream eagerly, then caches. Recursive tabled calls with the SAME ground key see an empty cache (the in-progress entry never exists), so they recurse and the host overflows. Fibonacci works only because each recursive call has a *different* key; cyclic `patho` and any genuinely self-recursive tabled predicate diverge. **Approach** — a small subset of SLG / OLDT resolution, enough to handle the demos in the brief. 1. **In-progress sentinel.** When a tabled call `T(args)` starts, store `(:in-progress nil)` under its key. Recursive calls into `T(args)` from inside its own computation see the sentinel and return only the answers accumulated so far (initially empty). 2. **Answer accumulator.** As each new answer is found, push it into the cache entry: `(:in-progress accumulated-answers)`. After a cycling caller returns, the outer continuation can re-consult the updated cache. 3. **Fixed-point iteration.** The driver repeatedly re-runs the goal until no new answers appear in a full pass, then transitions the cache from `:in-progress` to `:done`. 4. **Subgoal table.** Track (subgoal, last-seen-cache-version) per subscriber so each consumer only re-reads what it hasn't seen. **Suggested artefacts.** - `lib/minikanren/tabling-slg.sx` — new module with `table-slg-2` (parallel to `table-2` from naive tabling). Keep `table-2` working unchanged so Fibonacci/Ackermann don't regress. - `lib/minikanren/tests/cyclic-graph-tabled.sx` — the canonical demo: two-cycle `patho` from a→b→a→b plus a→b→c. With SLG, `(run* q (tab-patho :a :c q))` returns the single shortest path, not divergence. - `lib/minikanren/tests/mutual-recursion.sx` — even/odd via mutual recursion (`even-o n` ↔ `odd-o (n-1)`), tabled at both names. **Reference reading.** - TRS chapter on tabling. - "Tabled Logic Programming" — Sagonas & Swift (the XSB / SLG paper). - core.logic's `tabled` macro for an SX-dialect-friendly precedent. **Risk.** This is the brief's "research-grade complexity, not a one-iteration item". Plan for 4–6 commits: in-progress sentinel, answer accumulator, fixed-point driver, then one demo per commit. ### Piece B — Phase 6 polish: bounds-consistency for `fd-plus` / `fd-times` **Problem.** Current `fd-plus-prop` and `fd-times-prop` propagate only when two of three operands walk to ground numbers. When all three are domain-bounded vars, the propagator returns `s` unchanged — search has to label down to ground before any narrowing happens. **Approach** — narrow domains via interval reasoning even when no operand is ground. For `(fd-plus x y z)` with bounded x, y, z: - `x ∈ [z.min − y.max .. z.max − y.min]` - `y ∈ [z.min − x.max .. z.max − x.min]` - `z ∈ [x.min + y.min .. x.max + y.max]` For `(fd-times x y z)`: same shape, but with multiplication; need to handle sign cases (negative domain ranges) and the divisor-when-not-zero constraint already in place. **Suggested artefacts.** - Patch `fd-plus-prop` and `fd-times-prop` in `lib/minikanren/clpfd.sx` with new `:else` branches that compute new domain bounds and call `fd-set-domain` for each var. - New tests in `lib/minikanren/tests/clpfd-plus.sx` / `clpfd-times.sx` exercising the all-domain case: two domain-bounded vars in the body of a goal, with no labelling, after which their domains have narrowed. - A demo: cryptarithmetic puzzle (see Piece D) using bounds consistency to avoid labelling explosion. **Risk.** Low. The math is well-known; just careful min/max arithmetic and watch for edge cases (empty domain after narrowing). ### Piece C — `=/=` disequality with constraint store **Problem.** `nafc` is sound only on ground args; `fd-neq` only on FD domains. There is no general-purpose Prolog-style structural disequality `=/=` that works on logic terms. **Approach.** Generalise the FD constraint store to a uniform "constraint store" that carries: - domain map (existing) - *pending disequalities* — a list of `(u v)` pairs that must remain non-unifiable under any future extension. After every `==` / `mk-unify`, re-check each pending disequality: - If `(u v)` are now unifiable, fail. - If they're now structurally distinct (no shared substitution can unify), drop from the store (the constraint is satisfied). - Otherwise leave in store. **Where it bites.** The kernel currently uses `mk-unify` everywhere. Either: 1. Replace `mk-unify` with a constraint-aware wrapper everywhere (intrusive, but principled). 2. Keep `mk-unify` for goals that don't use `=/=`, and provide a parallel `==-cs` / `=/=-cs` pair plus an alternative `run*-cs` driver that fires the constraint check after each binding. Option 2 mirrors the `fd-fire-store` pattern and stays out of the common path. **Suggested artefacts.** - `lib/minikanren/diseq.sx` — disequality store on top of the existing `_fd` reserved key (re-using the constraint list, just with disequality-shaped closures instead of FD propagators). - `=/=` goal that posts a disequality and immediately checks it. - `=/=-test` integration: rewrite a few Phase 5 puzzles using `=/=` instead of `nafc + ==`. - Tests covering: ground-pair fail, partial-pair satisfied later by binding, partial-pair *contradicted* later by binding. **Risk.** Medium. The hard cases are *eventual* unifiability — a disequality `(=/= (cons a 1) (cons 2 b))` should hold until both `a` gets bound to `2` and `b` gets bound to `1`. Implementations like core.logic's encode this as a list of "violating bindings" the disequality remembers. ### Piece D — Bigger CLP(FD) demos: send-more-money + Sudoku 4×4 **Problem.** The current N-queens demo only verifies the constraint chain end-to-end. The brief's full Phase 6 list includes "send-more-money, N-queens with CLP(FD), map coloring, cryptarithmetic" — most of which exercise *more* than just `fd-neq + fd-distinct`. **Approach.** Two concrete puzzles that both stress bounds-consistency (Piece B) once it lands: #### send-more-money ``` S E N D + M O R E --------- M O N E Y ``` 8 distinct digits ∈ {0..9}, S ≠ 0, M ≠ 0. Encoded as a sum-of-digits equation using `fd-plus` + carry chains. Without Piece B (bounds-consistency), the search labels every digit combination upfront — slow but tractable on a fast machine. With Piece B, the impossible high-digit cases prune early. Test: a single solution `(9 5 6 7 1 0 8 2)`. #### Sudoku 4×4 Easier than 9×9 but exercises the full pattern: - 16 cells, each ∈ {1..4} - 4 rows, 4 cols, 4 2×2 boxes — 12 `fd-distinct` constraints - Some cells fixed as clues A small solver should handle 4×4 in well under a second once bounds-consistency narrows columns / boxes after each label step. **Suggested artefacts.** - `lib/minikanren/tests/send-more-money.sx` — single-solution test. - `lib/minikanren/tests/sudoku-4x4.sx` — at least three cluesets: unique solution, multiple solutions, no solution. - Optional: `lib/minikanren/sudoku.sx` with a parameterised `sudoku-n` for both 4×4 and a 9×9 stress test. **Risk.** Low–medium for 4×4 + send-more-money once Piece B lands. 9×9 Sudoku is a stretch; treat it as a stretch goal once the smaller demos are green. ## Suggested ordering 1. **Piece B first** (bounds-consistency for `fd-plus` / `fd-times`). Self-contained, low-risk, and unlocks Piece D's harder puzzles. 2. **Piece D** (the two demos). Validates Piece B with concrete puzzles. Doubles as the brief's missing canary tests. 3. **Piece C** (`=/=`). Independent track; once shipped, refactor the pet/diff puzzles in Phase 5 to use it instead of nafc. 4. **Piece A** (SLG tabling). Last because it's the highest-risk piece; do it when the rest of the library is stable so regressions are easy to spot. ## Operating ground rules (carry over from the original brief) - **Scope:** `lib/minikanren/**` and the two plan files (this one and the original). - **Commit cadence:** one feature per commit. Short factual messages (`mk: piece B — bounds-consistency for fd-plus`). - **Plan updates:** tick boxes here as pieces land; mirror status in `plans/minikanren-on-sx.md` Roadmap. - **Test discipline:** every commit ends with the cumulative count green. No-regression rule from the original brief still applies. - **`sx-tree` MCP only** for `.sx` edits. `sx_validate` after every structural edit. - **Pushing:** `origin/loops/minikanren` only. Never `main`. Squash to `architecture` only with explicit user permission, as we did for the v1 merge.