Skip to content

Commit 9cf5280

Browse files
committed
MeTTa TS 1.1.3
1 parent 5b1cca2 commit 9cf5280

46 files changed

Lines changed: 2747 additions & 294 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ A representative slice (wall-clock, subprocess including startup; `speedup` = Pe
302302

303303
The full per-program table is in [`RESULTS-corpus.md`](packages/node/bench/RESULTS-corpus.md).
304304

305+
Patrick Hammer also supplied four adversarial nondeterministic programs that were slow or exhausted memory in an older build. [`packages/node/bench/nondeterminism.mjs`](packages/node/bench/nondeterminism.mjs) keeps those query shapes as a separate checked benchmark. Five-run subprocess medians, including startup:
306+
307+
| Program | PeTTa | MeTTa TS | Speedup |
308+
| -------------------------------- | --------: | --------: | --------: |
309+
| filtered `matespacefast` matches | 5738.1 ms | 3344.2 ms | **1.72x** |
310+
| 22^4 `superpose` cross product | 388.7 ms | 148.5 ms | **2.62x** |
311+
| nondeterministic tabled `fib(7)` | 180.1 ms | 99.6 ms | **1.81x** |
312+
| duplicate-heavy `TupleConcat` | 178.8 ms | 101.1 ms | **1.77x** |
313+
314+
The benchmark validates all 234,256 cross-product results, all 196 distinct Fibonacci answers, the exact `TupleConcat` result, and the embedded matespace assertion. MeTTa TS uses the same default evaluator as the CLI. No benchmark, PeTTa, curry, or tabling mode is selected.
315+
316+
Automatic tabling does not memoize every recursive function. Admission requires transitive purity and a recursive strongly connected component that branches back into itself at least twice. Linear recursion such as factorial stays on the compiled path. Custom host operations and space, state, file, random, time, import, and output operations are excluded.
317+
318+
Completed and active tables share hard entry, answer, retained-cell, per-entry, and interner limits. Completed entries are LRU-evictable. Active entries cannot be evicted while their producer runs, so a call that cannot fit returns `TableResourceLimit` instead of continuing toward process exhaustion. See [Scaling to millions of atoms](website/advanced/scaling.md#automatic-tabling) for the current limits and completion policy.
319+
305320
That speed comes from general engine work:
306321

307322
- an O(1)-stack reduce-loop trampoline;
@@ -310,7 +325,10 @@ That speed comes from general engine work:
310325
- an O(1)-stack worklist for nondeterminism;
311326
- ground-atom type memoisation;
312327
- an exact-match ground-fact index;
313-
- bounded automatic tabling of pure overlapping-recursive functions, with structural token keys and runtime rule-versioned entries;
328+
- nested argument-functor indexing for ground runtime facts such as `(num (M $x))`;
329+
- bounded automatic tabling of pure overlapping-recursive functions, with structural token keys, runtime rule-versioned entries, and one shared budget for completed and active tables;
330+
- consumer-directed distinct memoization and streaming choice deduplication only where `unique-atom(collapse(...))` makes duplicate derivations unobservable, while ordinary `collapse` keeps its exact ordered bag;
331+
- a slot-based evaluator for closed pure `let` and `superpose` products that preserves result order and multiplicity without allocating general binding frames;
314332
- a native-code compiler for the pure deterministic int/bool/tuple subset, with tail-recursion compiled to loops and PeTTa-style **higher-order specialisation** so a function passed as an argument (e.g. `iterate`'s `$step`) is bound and compiled rather than interpreted;
315333
- a compiler for **nondeterministic `let*`-chain functions** (the backward-chainer class): a multi-equation function whose clause bodies chain space matches and recursive calls compiles to a clause-major depth-first search, the same fragment PeTTa hands to Prolog's clause alternatives;
316334
- a compiler for **add-atom saturation loops**: the add-if-absent idiom becomes one exact-membership probe plus append, and a single-branch `case` over a space match becomes a snapshot-and-thread loop with Empty-pruned branches.

RELEASE_NOTES.md

Lines changed: 129 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,152 @@
1-
# MeTTa TS 1.1.2
1+
# MeTTa TS 1.1.3
22

3-
A pure-TypeScript implementation of [MeTTa](https://metta-lang.dev), the
4-
OpenCog Hyperon language. The core engine runs in the browser, Node, Deno, Bun,
5-
edge runtimes, and TypeScript-based agents with no native addon and no required
6-
WASM. Optional host adapters can load Python or Prolog runtimes when a program
7-
asks for them.
3+
MeTTa TS 1.1.3 improves nondeterministic evaluation and bounds every automatic
4+
table allocation. The default evaluator is faster than PeTTa on the four
5+
reported nondeterministic workloads while preserving Hyperon semantics. There
6+
is no benchmark mode, PeTTa mode, curry mode, or manually selected fast path.
87

98
## Tested on Linux
109

1110
This release is prepared on Linux with Node 22 and pnpm 11. The release gate
1211
builds every package, typechecks the workspace, runs the full Vitest suite,
13-
builds the GitHub Pages documentation, runs the oracle checks, and runs the
14-
benchmark suite before tagging. Optional Python, Pyodide, SWI-Prolog, and
15-
SWI-WASM adapters are built and covered by their available tests, with live
16-
runtime checks depending on the host tools installed in the release
17-
environment.
18-
19-
The current conformance rerun reports Core/ST at 431 passed, 77 failed, 60
20-
expected failures, and 0 skipped. Full/ST with `fileio,json,random` reports 470
21-
passed, 174 failed, 4 expected failures, and 26 skipped. The remaining gaps are
22-
the tracked parser/directive, kernel, typing, stdlib, feature, and concurrency
23-
divergences, so this release should not be described as full Hyperon
24-
conformance.
25-
26-
## What's new
27-
28-
### Integrated host interop release train
29-
30-
This release includes the full local host-interop train that followed 1.1.0:
31-
browser host adapters, Python and Prolog runtime splits, SWI-WASM, Pyodide,
32-
source runners, async effects, CLI checks, and the eDSL host builders.
33-
34-
### Browser Python and Prolog
35-
36-
`@metta-ts/browser/host` composes optional host runtimes into the browser
37-
runner. A browser app can now run the same `.metta` program shape as Node:
38-
39-
```metta
40-
!(import! &self "math.py")
41-
!(py-call (math.add 40 2))
42-
43-
!(import! &self "facts.pl")
44-
!(prolog-call (edge alice $x))
45-
```
46-
47-
Python runs through `@metta-ts/py/pyodide`. Prolog runs through
48-
`@metta-ts/prolog/swi-wasm`. The base `@metta-ts/browser` package stays
49-
runtime-agnostic; Pyodide and SWI-WASM are only included when their adapter
50-
subpaths are imported.
51-
52-
### Prolog interop package
53-
54-
`@metta-ts/prolog` is now part of the release. The root package contains the
55-
generic bridge and MeTTa-side helper source. Runtime adapters live on subpaths:
56-
57-
- `@metta-ts/prolog/swi-node` talks to a local `swipl` executable.
58-
- `@metta-ts/prolog/swi-wasm` runs through `swipl-wasm`.
59-
60-
The supported surface follows PeTTa where the operation is a host Prolog bridge:
61-
`Predicate`, `callPredicate`, `assertaPredicate`, `assertzPredicate`,
62-
`retractPredicate`, `prolog-call`, `prolog-consult`, and
63-
`import_prolog_function`.
64-
65-
MeTTa TS keeps Hyperon-style evaluation. There is no PeTTa mode and no curry
66-
mode. Plain `.pl` imports and predicate calls are host capabilities, not a
67-
second evaluator.
12+
builds the GitHub Pages documentation, runs the Hyperon oracle, runs the scale
13+
proof, and runs the benchmark suite before tagging.
6814

69-
### Runtime adapter split
15+
The final suite passed 109 test files and 1,063 tests, with 38 optional live
16+
integration tests skipped. The checked 270-assertion oracle passed all 23
17+
corpus files.
7018

71-
The Python and Prolog roots no longer import runtime backends from their package
72-
roots. Node-specific adapters are explicit subpaths:
73-
74-
- `@metta-ts/py/pythonia`
75-
- `@metta-ts/prolog/swi-node`
76-
77-
Browser adapters are explicit subpaths:
78-
79-
- `@metta-ts/py/pyodide`
80-
- `@metta-ts/prolog/swi-wasm`
81-
82-
That keeps default imports browser-clean and leaves optional dependencies behind
83-
their adapter subpaths.
84-
85-
### eDSL host helpers
86-
87-
`@metta-ts/edsl/py` and `@metta-ts/edsl/prolog` provide dependency-free builders
88-
for the host interop forms. They build ordinary atoms such as `py-call`,
89-
`py-atom`, `prolog-call`, `Predicate`, and `import_prolog_function`. They do
90-
not load Python, Prolog, Pyodide, SWI-WASM, or Node adapters.
91-
92-
```ts
93-
import { vars } from "@metta-ts/edsl";
94-
import { pyCall } from "@metta-ts/edsl/py";
95-
import { prologCall } from "@metta-ts/edsl/prolog";
96-
97-
const { x } = vars();
98-
99-
pyCall("math.add", 40, 2); // (py-call (math.add 40 2))
100-
prologCall(["edge", "alice", x]); // (prolog-call (edge alice $x))
101-
```
102-
103-
### Bounded structural tabling
104-
105-
Automatic tabling now uses structural token keys and a bounded table space
106-
instead of recursive printed-form keys. Pure overlapping-recursive calls can be
107-
memoized without table growth being unbounded:
108-
109-
- completed pure entries are capped and LRU-evictable;
110-
- interner resets invalidate old opaque table keys;
111-
- runtime rules are version-keyed;
112-
- impure, meta, state, import, `match`, `collapse`, and concurrency paths stay
113-
outside the table cache;
114-
- direct active variant recursion uses local-linear completion for finite answer
115-
sets;
116-
- non-cyclic calls keep exact ordered-bag memoization.
19+
The external Core/ST conformance result is unchanged from the untouched 1.1.2
20+
baseline: 431 passed, 77 failed, 60 manifest expected failures, and 0 skipped.
21+
The remaining failures are existing parser, directive, kernel, typing, and
22+
stdlib contract differences. This release does not claim full specification
23+
conformance.
11724

118-
The stale-key path reported during release review is covered by a regression:
119-
old tail-call pending keys cannot write into a new interner generation after a
120-
table-space reset.
25+
## Nondeterministic execution
26+
27+
The new checked benchmark keeps four query shapes reported by Patrick Hammer
28+
as ordinary `.metta` files. Five-run subprocess medians include startup:
29+
30+
| Program | PeTTa | MeTTa TS | Speedup |
31+
| -------------------------------- | --------: | --------: | ------: |
32+
| filtered `matespacefast` matches | 5738.1 ms | 3344.2 ms | 1.72x |
33+
| 22^4 `superpose` cross product | 388.7 ms | 148.5 ms | 2.62x |
34+
| nondeterministic tabled `fib(7)` | 180.1 ms | 99.6 ms | 1.81x |
35+
| duplicate-heavy `TupleConcat` | 178.8 ms | 101.1 ms | 1.77x |
36+
37+
The harness validates 234,256 cross-product results, 196 distinct Fibonacci
38+
answers, the exact `TupleConcat` sequence, and the embedded matespace
39+
assertion. Run it with `pnpm bench:nondeterminism`.
40+
41+
A slot-based choice evaluator handles closed pure `let`, `let*`, `superpose`,
42+
integer arithmetic, comparisons, `if`, and constructor tuples. Unsupported,
43+
redefined, ill-typed, async, or executable-grounded forms stay on the normal
44+
interpreter path. Result order and multiplicity are unchanged.
45+
46+
`unique-atom(collapse(call))` can evaluate a supported static pure integer
47+
recurrence as a first-seen answer set. Closed pure choice products also retain
48+
first-seen answers as they emit instead of materializing a duplicate bag first.
49+
An ordinary `collapse(call)` still returns its exact ordered bag with duplicate
50+
derivations. Ground answer deduplication uses structural hashes with equality
51+
checks instead of a quadratic scan.
52+
53+
## Bounded automatic tabling
54+
55+
Automatic table admission remains conservative. The whole rule dependency
56+
graph must be pure, the call key must be safe, and the recursive component must
57+
branch back into itself at least twice. Linear recursion stays on the normal
58+
compiled path.
59+
60+
The policy does not assume that every recursive program is safe to memoize and
61+
does not let admitted tables grow until the process runs out of memory. It
62+
combines the static overlap test with one global runtime budget. Exceeding the
63+
active-state budget returns `TableResourceLimit`; it does not continue toward
64+
an out-of-memory failure.
65+
66+
Completed and active tables now share these default ceilings:
67+
68+
- 50,000 entries
69+
- 1,000,000 answers
70+
- 1,000,000 retained atom cells
71+
- 100,000 cells in one entry
72+
- 250,000 interned leaves
73+
74+
Completed tables are removed in least-recently-used order. Active tables are
75+
not evicted while their producer runs, so they return `TableResourceLimit` when
76+
the shared budget cannot fit more state. The consumer-directed recurrence memo
77+
uses the same entry, answer, cell, and per-entry limits. Interner generations
78+
prevent stale tail-call keys from writing after a reset.
79+
80+
Direct active variant recursion still uses local-linear fixed-point completion.
81+
Non-cyclic calls preserve exact ordered bags. The evaluator does not infer
82+
Picat-style `min` or `max` answer subsumption.
83+
84+
## Matching and scale
85+
86+
Ground runtime facts now have a nested argument-functor index. A pattern such
87+
as `(num (M $x))` selects the `M` bucket instead of scanning every `num` fact.
88+
The matcher falls back to complete candidates when a non-ground fact could
89+
unify.
90+
91+
A finite in-memory match whose result is discarded by a standard
92+
`let ... (empty)` is removed before enumeration. The optimization declines for
93+
custom grounded matchers, mutable state handles, changed standard forms, and
94+
non-memory spaces.
95+
96+
The 30,000-fact scale gate also runs larger actual MeTTa workloads:
97+
98+
| Program | Checked result | Time |
99+
| -------------------------- | ------------------------------: | -------: |
100+
| 24^4 pure choice product | 331,776 answers | 81.2 ms |
101+
| duplicate tuple product | 50 values from 500,000 branches | 30.8 ms |
102+
| nondeterministic `fib(10)` | 2,817 distinct answers | 73.4 ms |
103+
| nested runtime match | 30,000 answers | 647.3 ms |
104+
105+
## Correctness fixes
106+
107+
- `superpose` in the choice evaluator now strips a collapsed bag's leading
108+
comma marker. The recursive `supercollapse` corpus case remains empty as
109+
Hyperon requires.
110+
- Choice planning now respects application type errors, expression-headed
111+
rewrite rules, and replaced sync or async grounded operations.
112+
- Nested indexing no longer changes candidate enumeration when a pattern has
113+
no nested-head constraint.
114+
- Active table entries and answers count against the same global resource
115+
budget as completed entries.
116+
- The purity firewall now treats custom sync and async grounded operations as
117+
effectful unless they are the unchanged implementation of a known-pure
118+
built-in. File, catalog, random, time, output, fresh-identity, and host calls
119+
cannot enter automatic tables transitively.
120+
- File handles can be closed immediately with `file-close!` and are also closed
121+
when their grounded atom is collected. Dictionary spaces and file records use
122+
weak-key storage instead of lifetime-unbounded global maps. Grounded behavior
123+
and non-default grounded types remain on the lossless atomspace path.
124+
- Grounded-operation registration invalidates evaluated terms, table analyses,
125+
and compiled closures that may encode the previous dispatch behavior.
126+
- DAS gateway binding responses must contain exactly one MeTTa atom per value.
127+
Malformed or multi-atom wire values now fail at the decode boundary.
128+
- Git imports pass an end-of-options marker before the repository path.
129+
- The unused `streamEmit`, `tableBackchain`, and `trieSpace` experimental
130+
options have been removed. They never selected an implementation.
121131

122132
## Install
123133

124134
```bash
125-
npm install @metta-ts/core
126-
npm install -g @metta-ts/node
127-
npm install @metta-ts/py pythonia
128-
npm install @metta-ts/prolog
135+
npm install @metta-ts/core@1.1.3
136+
npm install -g @metta-ts/node@1.1.3
129137
```
130138

131-
Browser projects that use optional host runtimes should also install the runtime
132-
adapter they import:
139+
Optional host packages use the same version:
133140

134141
```bash
135-
npm install pyodide swipl-wasm
142+
npm install @metta-ts/py@1.1.3 pythonia
143+
npm install @metta-ts/prolog@1.1.3
136144
```
137145

138146
## Provenance
139147

140148
- Semantics: [hyperon-experimental](https://github.com/trueagi-io/hyperon-experimental).
141-
- Python interop surface: PeTTa's `py-call` and Hyperon's
142-
[`py-atom`](https://trueagi-io.github.io/hyperon-experimental/reference/atoms/)
143-
family.
144-
- Prolog interop surface: PeTTa-compatible predicate bridge forms where they do
145-
not depend on PeTTa's evaluator.
146-
- Verified spec and differential oracle:
147-
[LeaTTa](https://github.com/MesTTo/LeaTTa).
149+
- Verified differential semantics: [LeaTTa](https://github.com/MesTTo/LeaTTa).
150+
- Host compatibility: PeTTa-compatible Python and Prolog bridge forms where
151+
they do not depend on PeTTa's evaluator.
148152
- License: [MIT](LICENSE).

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"format": "prettier --write \"packages/*/src/**/*.ts\" \"*.{json,md}\"",
1717
"format:check": "prettier --check \"packages/*/src/**/*.ts\" \"*.{json,md}\"",
1818
"oracle": "vitest run packages/core/src/oracle.test.ts",
19-
"bench": "pnpm -r --filter @metta-ts/core --filter @metta-ts/node --filter @metta-ts/browser build && node packages/node/bench/suite.mjs && node packages/node/bench/concurrency.mjs && node packages/browser/bench/concurrency.mjs",
19+
"bench": "pnpm -r --filter @metta-ts/core --filter @metta-ts/node --filter @metta-ts/browser build && node packages/node/bench/suite.mjs && node packages/node/bench/nondeterminism.mjs --engine=ts --runs=1 && node packages/node/bench/concurrency.mjs && node packages/browser/bench/concurrency.mjs",
20+
"bench:nondeterminism": "pnpm -r --filter @metta-ts/core --filter @metta-ts/node build && node packages/node/bench/nondeterminism.mjs",
2021
"bench:scale": "pnpm -r --filter @metta-ts/core --filter @metta-ts/node build && node packages/node/bench/scale-proof.mjs",
2122
"bench:prolog": "pnpm -r --filter @metta-ts/core --filter @metta-ts/hyperon --filter @metta-ts/prolog --filter @metta-ts/node build && node packages/node/bench/prolog.mjs"
2223
},

packages/browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/browser",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/core",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",

packages/core/src/atom-set.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// SPDX-FileCopyrightText: 2026 MesTTo
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
import fc from "fast-check";
6+
import { describe, expect, it } from "vitest";
7+
import { alphaEq } from "./alpha";
8+
import { type Atom, expr, gfloat, gint, sym, variable } from "./atom";
9+
import { dedupAlphaStable, dedupExact } from "./atom-set";
10+
import { format } from "./parser";
11+
12+
const atomArb: fc.Arbitrary<Atom> = fc.letrec<{ atom: Atom }>((tie) => ({
13+
atom: fc.oneof(
14+
{ depthSize: "small", withCrossShrink: true },
15+
fc.constantFrom("a", "b", "c", "pair").map(sym),
16+
fc.constantFrom("x", "y", "z").map(variable),
17+
fc.integer({ min: -20, max: 20 }).map(gint),
18+
fc.integer({ min: -20, max: 20 }).map(gfloat),
19+
fc.array(tie("atom"), { maxLength: 4 }).map(expr),
20+
),
21+
})).atom;
22+
23+
function naiveAlphaDedup(atoms: readonly Atom[]): Atom[] {
24+
const out: Atom[] = [];
25+
for (const atom of atoms) if (!out.some((candidate) => alphaEq(candidate, atom))) out.push(atom);
26+
return out;
27+
}
28+
29+
describe("stable atom sets", () => {
30+
it("matches a quadratic alpha-equivalence oracle over random atoms", () => {
31+
fc.assert(
32+
fc.property(fc.array(atomArb, { maxLength: 80 }), (atoms) => {
33+
const expected = naiveAlphaDedup(atoms).map(format);
34+
const actual = dedupAlphaStable(atoms).map(format);
35+
return JSON.stringify(actual) === JSON.stringify(expected);
36+
}),
37+
{ numRuns: 500 },
38+
);
39+
});
40+
41+
it("keeps the first alpha-equivalent variable pattern", () => {
42+
const first = expr([sym("pair"), variable("x"), variable("x")]);
43+
const renamed = expr([sym("pair"), variable("y"), variable("y")]);
44+
const distinct = expr([sym("pair"), variable("y"), variable("z")]);
45+
46+
expect(dedupAlphaStable([first, renamed, distinct]).map(format)).toEqual([
47+
"(pair $x $x)",
48+
"(pair $y $z)",
49+
]);
50+
});
51+
52+
it("uses the evaluator's numeric equality for exact grounded atoms", () => {
53+
expect(dedupExact([gint(3), gfloat(3), gint(4)]).map(format)).toEqual(["3", "4"]);
54+
});
55+
});

0 commit comments

Comments
 (0)