Repository navigation
Expand file tree
/
Copy pathllms.txt
More file actions
471 lines (391 loc) · 24.1 KB
/
Copy pathllms.txt
File metadata and controls
471 lines (391 loc) · 24.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# MeTTa in Python
> Write Python, get MeTTa. Atoms are Python values, spaces are queryable
> stores, equations rewrite, and a Python function can be a MeTTa function.
> This file is how to USE the seat; the repository root's llms.txt covers the
> language and every other surface. `metta.llms()` prints that file the way
> `help()` prints, from an install as well as a checkout, and
> `python -m metta llms` is the same document from a shell. Every door named
> here exists on the object it is shown on, which `check.sh`'s `llms` lane
> checks against the live library rather than trusting this text.
## Install
```sh
pip install pymetta # from PyPI
pip install . # from the repository root; the engine and libraries are bundled
```
Or point `METTA_PATH` at a clone and import from there. You need Python 3.12+
and the PATCHED SWI-Prolog the engine checks for at boot, with its janus
bridge built against it: a stock SWI is refused by name, listing the patches it
lacks. The Linux x86_64 wheels for CPython 3.12, 3.13 and 3.14 carry that host
and bridge; anywhere else, build them as the repository's `docs/patched-host.md`
describes and install `pymetta[engine]`.
## First call
```python
from metta import MeTTa, S, V, G, ground, Expression, equation, rules, parse, forms
m = MeTTa() # binds &self
m.self.run("(= (f) 1) !(f) !(+ 1 2)") # [[Grounded(1)], [Grounded(3)]]
m.close() # or `with MeTTa() as m:`
```
`m` is the CONTEXT and `m.self` its home space. The context carries the
EVALUATION doors and forwards them home, `run`, `eval`, `match`, `add`,
`define`, `op`, `transaction`, `limits`, `stats` and their kin, plus the
collection protocols, so `len(m)`, `atom in m` and `m[pattern]` read the home
space directly. Storage and introspection stay on the space: `atoms`, `type`,
`digest`, `is_function`, `arities`, `builtins` and `space_names` are reached as
`m.self.atoms()`, and asking the context for one says so rather than raising a
bare attribute error. Two contexts are independent engines' worth of state in
one process, so a library never has to own the global one.
## Atoms: four kinds, no strings
A string is text. Everything structural comes from a factory, so a rename is a
rename and the type checker can see it.
```python
S.parent(S.alice, S.bob) # Expression (parent alice bob)
S.alice # Symbol
V.x # Variable $x
G(3.5) # Grounded: a Python value crossed as itself
ground(numpy_array) # the same, explicit
Expression((S.a, S.b)) # built from a sequence
S["odd?"] # a head outside Python's identifier grammar
parse("(parent $x bob)") # exactly one form
forms("(a b) (c d)") # every top-level form, none evaluated
```
`atom.metatype` answers `Symbol`, `Variable`, `Grounded` or `Expression`.
Atoms are immutable, hashable and comparable; `a.alpha_eq(b)` is equality
modulo variable renaming, and `a.alpha(b)` BUILDS the `(=alpha a b)` term
instead of answering now. Arithmetic and bitwise operators on an atom always
BUILD: `V.x + 1` is `(+ $x 1)` and `G(2) + G(3)` is `(+ 2 3)`, not `5`. Reach
for `.value` when you meant Python's own arithmetic. Rich comparisons are the
exception and answer Python booleans in the engine's term order; `a.lt(b)` and
its siblings build the comparison term.
## Asking
```python
kb.match(S.parent(V.x, S.bob)) # -> Answers, replayable; row.x is a binding
kb[S.parent(V.x, S.bob)] # the same, subscript spelling
kb.match(a, b) # a conjunction: one row per joint solution
m.eval(S.add(1, 2)) # -> [answer, ...] flat, EAGER
m.eval(t1, t2) # -> [[...], [...]] one GROUP per term
m.run(source) # -> one answer group per `!` in the text
kb.answers(term) # -> Answers, the lazy chunked cursor
kb.solve(4, V.x - 1).x # relational: runs let backwards, answers 5
kb.why(pattern) # why a pattern matched NOTHING
```
Return shapes are the thing you cannot guess. `match` and `answers` both hand
back `Answers`, which is lazy, replayable and chunked; `eval` is eager and
flat for ONE target but grouped for several, which is `run`'s own shape. A row
is a `Row`, a tuple: `row.x` reads a binding, and it unpacks and indexes. Answers
preserve MULTIPLICITY and do not promise order. `len(space)`,
`atom in space`, `for atom in space` and `bool(space)` (always True, a handle
is not a value that dwindles) are the container protocol; ask
`bool(space.match(p))` when the question is emptiness.
Tagged answers retain their derivation. `answer.under(algebra_object)`
reinterprets it using that object directly, including its Python operations,
without declaring it in the answer's space or querying again. A same-name local
declaration does not replace the supplied object. Inside combine, extend and
`where=` callbacks, `metta.current_algebra()` observes the selected carrier,
including an explicit `under=` that overrides an annotations row.
`under=counting` is the one carrier that answers a single aggregate row,
value `()` and annotation the number of derivations, instead of one row per
conclusion; `bag` has the same operations and keeps the rows.
`kb.match(q, under=metta.ranked, limit=2)` offers the provider the same bound
as `kb.match(q, under=metta.ranked)[:2]` and the language's `(top 2 ...)`.
The engine checks Exact routing, matching algebra and ordering, best-first
emission and repeatable consumption. A guarded or joined query keeps its full
candidate stream so limiting cannot discard an eventual answer. The two Python
doors take the ordinary route `(top 2 ...)` takes on a space that cannot
enumerate, so a provider that implements `match` and no `atoms()` receives
the bound through all three.
## Defining: two doors, and the price of each
```python
@m.define # LOWERED: the body BECOMES equations
def double(x):
return fn.mul(x, 2) # explicit engine relation
@m.op(effect="oracleIO") # CALLED: the engine calls into Python
def fetch(url): ...
m.define(prolog=source, name="p-sum") # a Prolog predicate as the function
equation(S.f(V.x)).to(V.x + 1) # one equation, built
rules(...) # a generator yielding equations
```
Measured per call on the extension-cost workload (`python -m
benchmarks.extension_cost` from `extensions/python`), which subtracts the
driver's own cost so a row is the marginal cost of one call:
| door | inferences/call | microseconds/call |
|---|---|---|
| translator rule (a macro) | 0.00 | below the driver |
| C foreign predicate | 1.00 | 0.02 |
| Prolog grounded predicate | 2.00 | 0.03 |
| an ordinary MeTTa function | 3.00 | 0.06 |
| `@m.define`, annotated | 3.00 | 0.07 |
| `m.op`, `transport="raw"` | 12.00 | 1.15 |
| `m.op`, encoded | 20.00 | 4.29 |
| `@m.define`, no annotations | 28.00 | 5.47 |
Read the two columns differently. The inference column is deterministic, three
identical samples per tier across two whole runs on 2026-09-06, and it is what
the benchmark's committed baseline gates. The microsecond column came from the
same run at load average 12, so take it for the magnitude of the crossing and
not as a pin.
The two `@m.define` rows are the same door and nine times apart. An annotated
body compiles to a native operator and costs what an ordinary MeTTa function
costs; without annotations the operators go through Python's live protocol at
application time, which is a host crossing per operation. Annotate a compiled
body you care about the cost of.
So reach for `@m.define` when the body is expressible as MeTTa, and `m.op`
when it must stay Python: it touches the network, holds a file, or calls a
library you are not going to re-express. A called operation must DECLARE its
effect class, because the engine cannot see inside it, and only a
`pureStructural` one is cached without being asked. A written `!(memoize f)`,
`!(memoize-exact f)`, `!(tabled (f $x))` or `(cache f force)` is honoured
whatever the class says.
The ordered `EffectClass` members are: `pureStructural`, `readOnlyLookup`,
`nondeterministicReadOnly`, `writesState`, `oracleIO`. Those five are what
`effect=` takes and the names reflection answers with; there are no
shorthands, and a spelling outside the five is refused by name. Three retired
inputs still map in, `immutable`, `stable` and `volatile`, reaching
`pureStructural`, `readOnlyLookup` and `oracleIO`. Registering a generator
lifts the class to at least `nondeterministicReadOnly` by itself.
## What a compiled BODY may say
There is no unsupported-construct list. `try`/`except`/`else`/`finally` and
`raise` compile onto the engine's error algebra, so `except ValueError as e`
matches live classes by identity and inheritance and `str(e)` reads as
Python's; dict and set literals are
`lib_dict` spaces, so `d[k]`, `k in d`, `d[k] = v`, `del d[k]`, `len(d)` and
the view methods all reach the library's vocabulary; comprehensions,
generators (`yield` is nondeterminism, one answer per yield), `match`,
walrus, `global`, and `type X = T` all compile.
Tuple and list assignment bind engine expression patterns; a shape mismatch
answers nothing. Generator `match` arms retain their captures in subsequent
yields. `case S.Empty` selects a subject that produced no answers.
`list(generator())` gathers a known compiled generator's answers into one
engine expression. `list(range(n))` returns a host list. If a deterministic
call could mean either returned data or collected answers, use `collapse(...)`
to collect answers, or bind the returned data before calling `list`.
`type(value)` queries the engine metatype after evaluating its operand;
`py(type(value))` requests the Python class. `typing.overload` stubs on one
compiled definition publish all distinct signatures with its fixed arity.
Generic operators (`+`, comparisons, bitwise operations, unary operations,
and builtins such as `sum`) invoke Python's live protocol at application
time. That preserves reflected methods and result species, and is classified
`oracleIO` because a dunder can have host effects. Use `fn.add`, `fn.lt`,
`fn.bit_and`, `fn.floor_div`, and sibling `fn` names when the body means an
engine relation that should remain pure, matchable, or reversible.
Anything else odd becomes a visible host island inside the equation, run per
application and never at decoration time, which is what `py(...)` spells
explicitly. A refusal cites its ground in one of the two languages rather
than a house rule, and renders the file, function, line and an exact caret.
One trap worth knowing: Python's `/` is true division, so a compiled `n / 2`
faithfully answers a float. The engine's own divisions are `fn.truediv(a, b)`
and `fn.floor_div(a, b)`.
## What a VALUE crosses as, a different choice
`py-atom` takes the image as an argument, independent of the door above: a
lowered body can still take an opaque argument.
```python
m.run('!(py-atom numpy.absolute Grounded)') # opaque: the live reference
m.run('!(py-atom numpy.absolute Expression)') # transparent: a snapshot
```
Transparent means translated into MeTTa structure. Opaque means carried whole
as a blob the engine holds and does not read, which keeps host identity and
skips a translation you may not want. Neither is the lesser one. An iterator
is always opaque, because measuring one drains it.
## Spaces
```python
kb = m.space(S.kb) # a handle; naming one does not create it
kb = m.space() # fresh and anonymous; a context manager that drops
kb += S.parent(S.alice, S.bob)
kb += [(S.Edge, 1, 2), (S.Edge, 2, 3)] # a fact STREAM: two atoms
kb += (S.Edge, 1, 2) # a tuple of scalars: ONE atom
```
Removal has three doors and they differ in how much they take and what they
say about absence:
```python
kb.remove(atom) # ONE occurrence, answers True/False
kb.remove(a, b, c) # the same grain, batched, answers how many were found
kb -= atom # ONE occurrence, no verdict: += and -= are inverses
del kb[pattern] # the DRAIN: every unifying occurrence, one crossing
# raises KeyError when nothing matched
```
`-=` is Python's in-place difference over a MULTISET, and Python's multiset is
`collections.Counter`, whose `-=` subtracts the multiplicity given rather than
clearing the key. MeTTa spells the pair `subtract-atom` and `remove-atom`.
```python
kb.transfer(x, y, to=other) # move one occurrence each, one transaction
kb |= other # bulk merge; a space is a multiset
kb.clear(); kb.drop() # empty it; end its whole life and free the name
kb.copy(); kb.digest() # clone contents; content identity
kb.source() # directly stored program as loadable MeTTa
kb.save("program.metta") # writes exactly kb.source()
kb.consumption("repeated") # writes (source <space> repeated) in the catalog
```
Dropping a space also retires its catalog declarations, including algebras,
annotations and law certificates. Reusing its name, including an anonymous
pooled name, starts a new life with no declarations inherited from the old one.
`python -m metta convert program.py [-o out.metta]` imports the module against
a fresh space and prints or writes its declarations as loadable MeTTa source.
`metta.spaces` composes without copying: `union(*spaces)` reads a set as one,
`overlay(front, back)` reads both and writes to the front, `readonly(s)`
refuses writes by capability, `mapped(s, shape)` is a shape view from one
declaration, `view(obj)` presents a live Python object as a space, and
`diff(a, b)` says HOW two spaces differ where `digest()` only says THAT they
do. `metta.foreign` is the other direction: implement `Matcher`/`Adder`/
`Remover` and register any backend as a space. `metta.remote` puts one across
a process boundary, `metta.tables` derives one from a table.
## The three seams anything plugs into
It is a KERNEL because the point is what plugs into it, and a thing you want
to use lands on one of three seams rather than needing a fork.
**Lower it.** Host code BECOMES equations: `@m.define` compiles a Python body
into MeTTa the engine reads, specialises and matches on, and a translator rule
adds syntax that costs nothing at run time.
**Extend it.** The engine CALLS your code: `m.op`, `m.register_prolog`,
`m.register_foreign_library` and a reader token class each make a host
callable or literal into an ordinary MeTTa one, priced 1 to 20 inferences a
call by how far the value travels, in the table above.
**Back it.** The atoms LIVE elsewhere: a space is an interface, so SQLite,
DuckDB, NetworkX, a live Python object, another process or a MORK store can
BE a space, and a query against one joins with a native one.
The third is the one people underestimate. A table-backed space is one
declaration, not a wrapper:
```python
kb = m.self # both take a SPACE, not the context
tables.declare(kb, "&crm", "(bridge (edge $a $b) (row edges (a $a) (b $b)))")
provider = TableBridge.from_context(kb, "&crm", sqlite3.connect(":memory:"))
metta.attach("&crm", provider) # the name is now a queryable space
```
Every provider operation derives from that one declaration, both directions:
a match becomes a WHERE, a repeated variable becomes `WHERE a = b`, `take`
pushes down a LIMIT, an add becomes an INSERT, and the declaration is itself
knowledge a program can query. `metta.foreign` is the hand-written form of
the same seam, `metta.remote` puts a space in another process, and
`metta.spaces.view` presents a live Python object as one.
Both directions of that seam also speak Arrow. A bridge answers
`__arrow_c_stream__`, so `pl.DataFrame(provider)` streams its declared columns
out of the provider's own cursor, and `tables.add(kb, "row", relation)` reads
any producer with that method a record batch at a time, which is how a DuckDB
relation or a Parquet reader loads. Query results answer it too, so
`pa.table(rows)` and `duckdb.sql("select * from rows")` read the answers
themselves; the producer needs `pip install 'pymetta[arrow]'` and a consumer
needs no pyarrow. `tables.sql_function(connection, kb.fn.dbl)` goes the other
way and makes a MeTTa head a scalar SQL function.
`extensions/python/examples/language-feature-examples/` holds a runnable
program per twinned MeTTa example, each at that example's path and checked
against the MeTTa it mirrors; an example without a twin is a backlog row
rather than a gap in the corpus.
They are the reference for idiomatic code on this surface.
## Types, and checking them
```python
m += typed(S.f, arrow(S.Number, S.Number)) # (: f (-> Number Number))
kb.type(atom) # the first get-type answer
kb.cast(value, S.Number) # runtime cast on the engine's rules
```
Annotations on a `@m.define` become the declaration, and a local annotation
becomes an enforceable in-place claim.
## Declared algebras
`space.algebra(name, combine=..., extend=..., zero=..., one=..., type=...)`
checks every input and result against a Python type, a MeTTa type atom, or a
predicate returning exactly one bool. Predicates receive symbols and
expressions as atoms, and grounded values as their Python payloads. The callable `metta.algebra` module
accepts the same `type=` and accepts Python callables as `plus=` and `times=`.
It declares into the current space, so `with space:` chooses its owner.
A type permits evaluation without a law certificate and grants no answer
fusion. Any law claim, including contraction, requires the complete finite
domain supplied with `carrier=`. Equational laws are checked by exhaustion,
never by sampling. You may combine
`type=` with `carrier=` to check both membership constraints. Tensor membership
and law equality compare shape and exact element values, never blob identity.
A law that cannot be checked is refused with the remedy: declare a finite
carrier for the certificate, or evaluate under `prov` and use `.under()` to
reinterpret the retained derivation, including for gradients.
An out-of-carrier value raises a refusal naming the algebra, value and remedy.
An operation returning an `Error` raises `algebra_operation_error`; the error
cannot become a tag. A bad grounded numeric call answers one error carrying
its specific type refusal.
## Concurrency, transactions, bounds
```python
from metta import spawn, par_map, race, channel, parallel
future = spawn(term) # a FutureSpace; wait(), settled(), cancel()
par_map(fn, items); race(a, b); channel()
m.transaction(term) # all-or-nothing; rollback delivers no events
m.transaction(callable) # one boundary over SEVERAL calls, writes included
with m.limits(inferences=10_000, timeout=1.0): ...
with m.speculate(): ... # run and discard, per CALL: the writes too
```
An abandoned future warns rather than spinning silently.
`with m.atomic():` and `with m.speculate():` on the context, or
`m.self.atomic()` and `m.self.speculative()` on a space, are per-CALL policies over
every door in the block, `add`, `remove`, `del m[pattern]`, `transfer` and
`clear` included: a speculative block leaves nothing behind and an atomic one
makes each call its own committing transaction. That is the whole contract,
so a later call does not see what an earlier one wrote under speculation, and
a raise does not undo a call an atomic block already committed. For
all-or-nothing across several calls the door is `m.transaction(callable)`,
and there is deliberately no `with m.transaction():`: SWI's transaction/1 and
snapshot/1 take a closed goal, and an engine, the one thing that suspends a
goal across host calls, refuses to yield inside either.
## Seeing what happened
```python
with m.stats() as s: ... # s.inferences (deterministic), cputime, walltime
m.trace(term) # the reduction trace; .stopped names the bound that cut it
m.trace(term, filter=[S.f]) # only the named functions are recorded
with m.debug(term, on=[S.f]) as d: # breakpoints; the loop body is the suspension
m.self.derivation(term) # the proof tree; premises and effects execute
m.self.speculative() # scope a derivation to discard engine writes
m.self.lint() # diagnostics over declarations, equations, calls
m.self.effect_plan(term) # named operations and joined effect, without execution
m.self.subscribe(pattern, cb, on="both") # a standing query; on= add/remove/both
m.self.watch(pattern) # the iterator face of the same
```
Inferences are deterministic and wall clock is not; gate on the former.
## The rest of the library, by module
`metta.aio` is the same engine without blocking an event loop (`AsyncMeTTa`).
The `metta-arrays` distribution (`pip install 'pymetta[arrays]'`), whose module
is `metta_arrays` and whose doors are a space's `arrays` namespace, crosses
arrays through DLPack and the Array API standard.
`Annotated[DLTensor, Shape(2, 3)]` refines an operation's arrow to
`(Annotated DLTensor (Shape (2 3)))`. A `(4 1)` argument returns `BadArgType`;
shared MeTTa dimension variables bind across parameters and the result.
The single refusal retains the actual shape, including through an algebra operation.
Live array values carry the same shaped type. `SHAPE_RULES` covers every
registered head: unary elementwise operations preserve shape, binary
operations use broadcasting, and rank-two `matmul` unifies inner dimensions.
Other operations expose an array result's actual shape; scalar observations
retain their host type. Unknown dimensions stay symbolic until argument or
live-value typing binds them.
`metta.structures` has the stores worth having with MeTTa's semantics at
Python speed: `AlphaSet`, `PatternMap`, `MatchIndex`, `LiveView`,
`ClosureView`, `TabledMap`. `metta.live` is the general materialised view
`m.live(*query)` answers, `Live` with `Delta` and `Changes` its stream, kept
out of `metta.structures` so the stores do not pay to build it; the `m.live`
door itself comes with `pip install 'pymetta[live]'`.
`metta.events` is the public event stream, its clock and its commit
boundaries, and `metta.subscribe` its two delivery models. `metta.convert` registers two-way
conversions and runs casts, `metta.integrate` is the
interface a library implements to work deeply with the engine, `metta.paths`
builds cycle-safe lazy structural paths, `metta.manifest` makes deployment
knowledge (`metta.boot(path)` assembles an app), `metta.derivation` and the
module-level `trace` door reify proofs and reductions, `metta.lint` reports diagnostics,
and `metta.testing` ships hypothesis strategies for property-testing code
built on this surface.
`m += lib.x` imports one of the engine's shipped MeTTa libraries, the roster
the repository root's llms.txt names, through the write
door, because a library IS knowledge and the receiver is the target space.
## When it refuses
Every refusal is typed and carries machine-readable parts beside the message:
`MettaError` is the base (with `atom`, `space`, `operation`, `capability`,
`ground`), `CompileError` names the construct and renders a caret, and
`SpaceCapabilityError` names the space, operation and missing capability,
and its sibling `PlatformCapabilityError` names what this build would have to
be made with and what its absence costs, which is the refusal a deployment
without `library(thread)` meets at `hyperpose`.
An error has two readings in MeTTa. An `(Error ...)` written literally, or an
answer bound before its next call, is data and can be compared or inspected.
A strict argument whose evaluation produces an `(Error ...)` instead finishes
the enclosing call with that error unchanged. Use `if-error`,
`return-on-error`, or an explicit `let` binding before treating the answer as
data; a parameter declared `Atom` receives its written argument without
evaluating it.
## The ladder, and it never shrinks
Python here, MeTTa source through `run`/`parse`, Prolog through
`m.register_prolog(source=..., path=..., names=...)`, and C through
`m.register_foreign_library(path, entry=, names=)`. Each rung is reachable
from the one above, and the fast path is the idiomatic one.
## Where to read next
- `website/tutorials/` teaches the language from Python, one idea at a time.
- `website/guide/` is task-oriented: queries, definitions, spaces, threads.
- `website/reference/` reproduces every public module's exact signatures.
- `extensions/python/examples/` are runnable programs that print checked claims.