Skip to content

Releases: Euclid-BG/Euclid-MCP

v0.4.6 — Backend parity, multi-line continuations, REPL hardening

Choose a tag to compare

@meob meob released this 23 Aug 11:07
ff56e6e

[0.4.6] — 2026-08-23

Fixed

  • Backend divergence on true/false body literalspred($x) IF false
    crashed the SWI-Prolog backend with an opaque engine_error: the
    meta-interpreter had no branch for the literal, so it reached clause/2,
    and SWI-Prolog 10 raises permission_error(access, private_procedure, fail/0) over the built-in false/fail; the engine loop's catch-all
    discarded the message. The native engine failed cleanly, so the same KB
    behaved differently per backend — and IF true was divergent in the other
    direction (succeeded on Prolog, failed natively). Both engines now give the
    literals explicit, identical semantics: IF false never proves, IF true
    proves like a fact (proof node fact, or rule when the rule carries an
    ID). This makes IF false the legitimate spelling for vocabulary
    declarations in rules-only KBs.
  • Backend divergence on unbound query variables — solutions from
    non-range-restricted rules (a head variable absent from the body, e.g.
    report($user) IF system_compromised, or negation over an empty declared
    predicate with a variable goal) were dropped by the native engine and
    stringified as SWI fresh tokens (_28498) — or lost entirely — by
    SWI-Prolog. Both engines now keep the solution and surface unbound query
    variables as explicit null bindings ({"any": null}). Proof trees are
    also aligned: SWI's nondeterministic fresh-variable names are normalized
    to the Euclid-IR wildcard _ (quoted literals masked, so user data is
    never rewritten), making proof trees byte-identical across backends and
    across runs.
  • Built-in body goals raised instead of failing — a rule body naming any
    SWI-Prolog built-in (e.g. atom_length) hit clause/2 and surfaced as an
    opaque engine_error. Both meta-interpreter branches now carry a
    \+ predicate_property(Goal, built_in) guard: since user clauses can never
    exist for a built-in, the goal fails cleanly — matching the native engine,
    where unknown predicates simply fail.
  • Zero-arity predicates leaked across engine loads_extract_pred_sig
    required parentheses, so bare statements (rainy, or a t IF true head)
    were never declared dynamic nor registered in the persistent engine's
    workspace; their clauses survived clear_workspace and duplicated on every
    subsequent load, silently multiplying solutions. Bare atoms now produce a
    /0 signature. Parity tests pin single-solution behavior across reloads.
  • Backend divergence on non-ASCII variables$città, $кто and any
    other Unicode variable now behave identically on both backends. The
    variable-name pattern (language.VAR_NAME_RE, single source of truth for
    the native parser, the Prolog translator and the linter) accepts Unicode
    letters, and the translator masks variables (__VAR_name__) during
    atom-quoting so they can never be wrapped into quoted atoms. Previously
    the native engine raised a parse error while SWI-Prolog silently
    truncated the name (or quoted it into an atom that unified with nothing).
  • NFC normalization at every entry pointparse() normalizes KB text
    and strip_query_prefix() normalizes explicit queries to NFC, so
    decomposed (NFD) spellings unify symmetrically on both backends instead
    of failing on the native engine and matching only byte-identical forms
    on SWI-Prolog.
  • --backend flag silently discarded an exported EUCLID_BACKEND — the
    CLI wrote the flag's default (auto) into the environment on every run,
    so EUCLID_BACKEND=native euclid-cli still launched SWI-Prolog. The flag
    is now applied only when explicitly passed: an exported value is honored,
    and an explicit --backend wins over it.
  • Multi-line rules with leading AND split into garbage statements — a
    rule continued with the conjunction at the START of each line (the common
    Prolog habit) was cut at its first complete body goal, while the leftover
    AND ... lines were accepted as facts by the lenient language parser and
    then rejected at runtime by both engines. The parser now absorbs
    AND-leading continuation lines (and rejects orphan ones with a clear
    message), and the interactive REPL holds a completed rule until the next
    input settles whether a continuation follows. Continuation detection is
    buffer-aware: a line trails the open statement when the buffered text ends
    with IF/AND or the line itself opens with AND, so trailing-style and
    leading-style multi-line rules both work in files, seeded KBs and live
    sessions (previously a continuation line without an inner IF, e.g.
    $y is $x - 1 AND, was flushed as its own statement).
  • :check could stay green on statements the engines refuse
    validation relied only on the lenient parser while both backends re-parse
    every statement with the strict term parser. run_check_kb now runs each
    fact, rule and the query through that same parser, so a green check
    guarantees the KB loads and queries on either backend.
  • REPL errors vanished into stderr — engine and parse errors printed
    only a terse message to stderr, easy to lose next to the tool-call log
    lines (and invisible in piped sessions). They now print to stdout.
  • String literals bind bare values on both backends — IR quoted values
    are translated to single-quoted Prolog atoms instead of SWI string
    terms; bindings are now identical to the native engine's bare content
    (e.g. müller, not "müller"). Operators inside literals were already
    inert data and remain so.

Changed

  • REPL banner renamedEuclid-MCP REPL …Euclid-IR REPL vX.Y.Z — type facts and rules, then ? query.. The REPL calls the tool functions
    in-process; no MCP server or protocol is involved, so the banner now names
    the language. The version comes from the in-tree package constant (with a
    new drift test against pyproject.toml), not from possibly stale install
    metadata.

Added

  • Reserved boolean keywordstrue / false join if, and, not,
    is as reserved words: rejected as predicate names, bare facts and rule
    heads with a clear message (previously such KBs broke the Prolog load with
    the same opaque error). As arguments they remain plain atom data.
  • Vocabulary-declaration idiom documenteddocs/EUCLID_IR.md documents
    the true/false literals and the load-once/query-many pattern where a
    rules-only stable layer declares its input vocabulary with IF false
    bodies (validated green by check_kb, zero inference impact); the PCI-DSS
    sample was migrated from the IF 1 > 2 workaround.
  • prova_unicode.py / divergenze.py — Unicode conformance suite and
    backend-divergence isolator used to pin the fixes above.
  • tests/test_true_false_literals.py — backend-parity suite for the boolean
    literals, negation over declared predicates, clean built-in failure, the
    zero-arity reload regression, and the end-to-end vocabulary +
    delta_knowledge flow.
  • tests/test_unbound_variables.py — backend-parity suite for unbound query
    variables: explicit null bindings, cross-backend proof-tree identity,
    run-to-run determinism, and quoted-data safety of the wildcard
    normalization.
  • tests/test_multiline_continuations.py — AND-leading rule continuations
    in parser and REPL, plus stdout visibility of engine errors.
  • tests/test_check_kb_engine_alignment.py — statements the engines reject
    are flagged at check time; unicode/quoted KBs stay green.
  • tests/test_backend_precedence.pyEUCLID_BACKEND survives a run
    without --backend; an explicit flag overrides it.
  • tests/test_version.py — package version matches pyproject.toml.
  • REPL: :kb listing gained a paste-safe counts header (# session KB: N facts, M rules) and the help documents the hidden :list alias.

v0.4.5 — Unicode atoms in the native engine

Choose a tag to compare

@meob meob released this 21 Aug 23:17
7e812ce

Added

  • Unicode atoms in the native engine — the pure-Python lexer
    (euclid_mcp/ir_parser.py) now accepts Unicode predicate/atom names
    (父(张三), смертный($x), Бог(Иван)), closing the last documented gap
    with SWI-Prolog (see docs/NATIVE_ENGINE.md). Numbers stay ASCII (0-9),
    so Unicode digits are read as name characters, and variables remain ASCII
    ($name) per the Euclid-IR spec — identical on both backends.
  • Added examples 09 and 11 - a Turing machine in pure Euclid-IR
    demonstrating Turing-completeness of the language and
    a Certainty factors implementation with MYCIN-style scoring and exact dyadic
    arithmetic.

Changed

  • Unicode parity teststest_reason_tool_unicode and
    test_what_if_unicode in tests/test_unicode_atoms.py lost their
    prolog_only marker and now run on both backends; the two
    native_only rejection tests were removed together with the limitation.
    New native-engine-level Unicode tests live in tests/test_native_engine.py
    (predicates, args, rules with rule IDs, negation, case sensitivity).

Fixed

  • Native engine crashed on division by zero$x is 1 / 0 raised an
    uncaught ZeroDivisionError that escaped the tool layer (which only
    catches RuntimeError), crashing the reason call instead of returning
    an error result. The native evaluator now raises a clean
    "Arithmetic error: division by zero", matching the Prolog backend's
    engine_error. A deep proof that exhausts the Python stack is likewise
    converted into a clear error asking to lower max_depth.
  • Native parser silently split malformed numeric literals — the lexer
    read only the leading decimal part of unsupported literal forms
    (1e3, 0x10, 1_000, 12abc) and the argument parser tolerated
    missing commas, so e.g. n(1e3) silently became the arity-2 term
    n(1, e3) and queries failed with zero solutions and no error. Both are
    now explicit parse errors; p(1-2) is rejected too instead of parsing as
    p(1, -2) (see docs/NATIVE_ENGINE.md). Regression tests in
    tests/test_native_engine.py.
  • Preloaded-KB digest dropped Unicode predicates — the predicate regex in
    euclid_mcp/kb_summary.py was ASCII-lowercase-only, so a preloaded KB with
    e.g. Бог(Иван) omitted that predicate from the server-instructions digest.
  • Prolog engine crashed on compound-term bindings — a query variable bound
    to a nested compound term (e.g. a structured state) made json_write/3
    raise type_error(json_term, ...), surfaced only as an opaque
    engine_error. Both snippet generators now route every binding through a
    new euclid_json_value/2 engine helper: atoms become strings, numbers pass
    through, other terms are rendered via term_string/2 — matching how the
    native engine already renders compound bindings. Regression test in
    tests/test_prolog_bridge.py.
  • Spurious arity warnings/circular-rule errors on nested-compound KBs
    check_kb counted all commas in a predicate's argument list, so nested
    compounds (cfg(run, tape(cell(1, blank)))) reported inflated arities and
    triggered false inconsistent_arity warnings. Arity counting is now
    depth- and quote-aware (shared _arity helper in euclid_mcp/validation.py,
    also used by the preloaded-KB digest). Additionally, the circular-rule check
    now accepts a variable-bearing fact as a legitimate base case for a
    recursive rule (e.g. final(cfg(done, $t), cfg(done, $t))).

v0.4.4

Choose a tag to compare

@meob meob released this 18 Aug 21:37
494ffdb

What's Changed

  • align: LSP server, examples, docs, and build fixes by @meob in #22
  • release: v0.4.4 by @meob in #23

Full Changelog: v0.4.3...v0.4.4

v0.4.3

Choose a tag to compare

@meob meob released this 16 Aug 21:27
0856d03

What's new

Added

  • Native-only Docker imageDockerfile.native on python:3.12-slim (no SWI-Prolog), pure-Python native engine (EUCLID_BACKEND=native); compose service euclid-mcp-native. ~283MB vs ~370MB full image. Verified in-container: MCP stdio handshake, reason, what_if.
  • Didactic guidedocs/DIDACTIC.md (12 chapters, incl. Beyond the 1:1 mapping: the audit layer) with reference links.

Fixed

  • CI now runs an explicit EUCLID_BACKEND=native leg — the native engine was previously never exercised in CI.
  • Unicode tests re-marked prolog_only (they assert SWI-Prolog Unicode behaviour); the native ASCII-only rejection is now asserted by test_reason_tool_unicode_rejected_native / test_what_if_unicode_rejected_native.
  • Backend-aware test_api.py health/metrics tests; EUCLID_BACKEND isolated in in-process CLI tests.

Full changelog: CHANGELOG.md

v0.4.2 — Interactive Euclid-IR REPL

Choose a tag to compare

@meob meob released this 16 Aug 06:57

What's new

Added

  • Interactive Euclid-IR REPLeuclid-cli with no subcommand opens a swipl/psql-style shell: facts and rules accumulate in a session knowledge base across ? query lines, multi-line rules continue after IF/AND (continuation prompt ... > ), and : meta-commands cover the remaining tools (:check, :kb, :load <file>, :explain [query], :diagnose <query> [why|why_not|what_needs], :what-if <mods>, :reset, :quit). Piped input runs the same loop as a batch script without prompts; -f/--knowledge seed the session (preload fallback when empty). Docs: README "Via CLI" and docs/CLI.md.

Full changelog: CHANGELOG.md

v0.3.1

Choose a tag to compare

@meob meob released this 13 Aug 07:19
aa1e72b

v0.3.1 — 2026-08-13

Added

  • HTTP API authentication & TLS (integrations/euclid_api.py): opt-in EUCLID_API_KEY (or --api-key) requires Authorization: Bearer <key> on every POST (constant-time check, 401 otherwise, /health stays open for load balancers), and EUCLID_TLS_CERT / EUCLID_TLS_KEY (or --certfile / --keyfile) serve HTTPS directly. Setup and posture documented in docs/PRODUCTION.md → "Authentication & TLS".
  • Python 3.13 and 3.14 support: verified end-to-end (lint, mypy, full test suite) on CPython 3.13 and 3.14; CI matrix now runs on Python 3.10–3.14.
  • Stress & soak benchmark (benchmarks/euclid_bench.py): detects response mixing, KB pollution, and engine errors across periodic restarts; regression detector for the load+query atomicity and the periodic-restart policy.
  • Benchmark documentation: one detail page per benchmark plus a catalog of results and implementation choices (benchmarks/BENCHMARKS.md).
  • SWI-Prolog compatibility matrix: full suite passes on SWI-Prolog 8.4.2, 9.0.4, 9.2.9, and 10.0.2 (benchmarks/docs/06-swi-prolog-versions.md).

Performance

  • KB preload optimization: repeated loads of the same knowledge base skip re-parsing and re-asserting. Python-side parse+translate results are cached per KB source (_translate_cached), and the engine skips the workspace rebuild when the load carries the same kb_hash (reply skipped:true). A repeated identical 20 000-fact KB drops from ~196 ms to ~18 ms per load. assert/retract invalidate the fingerprint.

Fixed

  • Engine workspace sweep is SWI-Prolog 9.x-safe: clear_workspace/0 now retracts only the registered workspace predicates (workspace_predicate/1) instead of every dynamic predicate. On SWI-Prolog 9.x the broad sweep corrupted the autoloader (domain_error(file_type, prolog)); the registry fix is ~19% faster (513 vs ~429 req/s).
  • Periodic engine restart: now fires before the next load, never after a crossing request, so a relaunched engine always receives its workspace first.
  • Load+query atomicity gap: PrologServer.load_and_query holds the lock across both steps — a single atomic exchange; the workers=4 benchmark flips from FAIL to PASS.

v0.3.0 — persistent SWI-Prolog engine, Unicode atoms

Choose a tag to compare

@meob meob released this 12 Aug 06:53

0.3.0

Persistent engine & performance

  • Persistent SWI-Prolog engine: single long-lived swipl process, JSON-lines pipe protocol, workspace reloaded per request instead of booting Prolog per call. Same API, identical solution ordering.
  • Streaming query results: solutions written straight to the engine output via json_write/3 — no double serialization, low memory on large result sets.
  • Benchmark: benchmarks/persistent_engine_benchmark.py — ~3×–42× steady-state speedup (mean ~14×).

Language (Euclid-IR)

  • Unicode atoms (\p{L}): CJK, Cyrillic, Greek… names, arguments and rule IDs, e.g. 父(张三), Бог(Иван). Case folding is ASCII-only.
  • Canonical rule-ID marker is now # RULE: (parser stays case-insensitive).

Docs

  • README "Scalability" section; IDEAS/CHANGELOG updates.

Requires SWI-Prolog system package (local / install server, not in-browser).

v0.2.0

Choose a tag to compare

@meob meob released this 11 Aug 08:13
57854d5

v0.2.0

  • explain tool: deterministic proof-tree → natural-language reasoning steps with rule ID citations (no LLM)
  • KB preload: EUCLID_KB_PATH / --kb-path + markdown digest via kb_summary
  • Rule IDs: # rule: <id> surfaced as rule_id in proof trees (audit trail)
  • HTTP API: new POST /explain endpoint
  • Docs aligned (README, EUCLID_IR, AGENTS, integrations)
  • TODO: lists feature planned next (scope: literals + member)