From 50eb7079e51abdf2516ecb6fc288b907a42c45fb Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 6 Jun 2026 18:30:44 +0000 Subject: [PATCH] =?UTF-8?q?briefings:=20mod-loop=20=E2=80=94=20cut/backtra?= =?UTF-8?q?cking=20allowance=20+=20sx=5Fwrite=5Ffile-first=20+=20loaded-en?= =?UTF-8?q?v/not(Goal)=20gotchas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make explicit that the loop may lean on Prolog backtracking (pl-query-all) and cut, preferring clause-order precedence via pl-query-one. Default to sx_write_file over path/pattern edits; flag that sx_insert_near drops all but the first form. Document the loaded-env primitive restriction (includes?/chars/etc. undefined after prolog preloads; use the tokenizer's surviving set) and that negation is the not(Goal) functor, not the prefix \+ operator. Co-Authored-By: Claude Opus 4.8 (1M context) --- plans/agent-briefings/mod-loop.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/plans/agent-briefings/mod-loop.md b/plans/agent-briefings/mod-loop.md index 2353de14..9f1e2c6c 100644 --- a/plans/agent-briefings/mod-loop.md +++ b/plans/agent-briefings/mod-loop.md @@ -66,7 +66,12 @@ Every iteration: implement → test → commit → tick `[ ]` → Progress log like a broken binary but is just a param mismatch. `sx_validate` after edits. Path-based edits (`sx_replace_node`) count comment headers in their indices and can clobber the wrong node — re-read after, or prefer `sx_write_file` for small - files. + files. **Default to `sx_write_file` (rewrite the whole file) over path/pattern + edits** — these are small files and the rewrite always parses-before-writing. + `sx_insert_near` inserts only the FIRST top-level form of a multi-form source + (it silently drops the rest; byte count barely moves) — never use it to add a + block of forms; rewrite the file instead. `sx_replace_by_pattern` is fiddly to + match — don't fight it, just rewrite. - **Unicode in `.sx`:** raw UTF-8 only, never `\uXXXX` escapes. - **Commit granularity:** one feature per commit. Short factual messages (`mod: spam-keyword policy rule → :hide + 6 tests`). Push to `origin/loops/mod`. @@ -82,8 +87,30 @@ Every iteration: implement → test → commit → tick `[ ]` → Progress log precedence explicit and deterministic (tests will depend on it). A "no rule matched" outcome must be a real, testable result (`:keep`), not a query failure you forget to handle. +- **You may lean on backtracking and cut.** The substrate is full Prolog — + `pl-query-all` gives every proven clause (use it for "strictest-wins" or + multi-match analysis), `pl-query-one` gives the first (clause order = precedence). + Cut (`!`) and the other control constructs are available if you need to prune + alternatives inside a body, but for rule precedence prefer plain clause ordering + resolved by `pl-query-one` — it's the clean, testable default. Don't hand-roll + precedence in SX when the engine's backtracking already gives it to you. - **Negative decisions need closed-world care.** "No evidence of violation" vs "evidence absent" differ. Be explicit about negation-as-failure where you use it. + In this substrate, negation is the **functor** `not(Goal)` / `\+(Goal)` — the + prefix `\+ Goal` operator does **not** parse. Unknown predicates *fail* (no + existence error), so a report lacking some fact safely falls through a rule that + references it. Quote user-data atoms (`'foo-bar'`) — a bare hyphen is the minus + operator and will misparse. +- **Loaded-env strips the high-level string prims.** After the prolog preloads are + loaded, the eval env loses `includes?`, `chars`, `str-join`, `keyword` and + friends — they are **undefined** (a function calling one fails only when called, + often mid-test-load, looking like a mystery crash). Only the set the Prolog + tokenizer itself uses survives: `slice`, `len`, `nth`, `=`, `join` (sep first: + `(join sep list)`), `downcase`, `map`, `reduce`, `append`/`append!`, `when`, + `cond`, `if`, `let`, `begin`, `get`, `dict-get`, `keys`, `empty?`, `first`, + `reverse`, `+`, `-`, `<`, `<=`. Build substring search yourself over `slice`/ + `len` (see `mod/str-contains?`). Treat `not`, `and`, `or`, `>` as suspect in + guest code unless you've confirmed them — nest `if`/`when` and use `(< a b)`. - **Lifecycle state is separate from policy.** Keep the state machine (Phase 3) as an SX module over the engine, not tangled into Prolog rules. - **Federation trust is advisory by default.** A peer's decision only binds locally