metta.trace
Source: extensions/python/metta/_trace.py.
The reduction trace as Python objects. m.trace(term) runs that term with every compiled MeTTa function wrapped engine-side, and answers TraceEvent records: a call carries the term entering reduction at its nesting depth, the matching exit carries the answer, and a call with no exit is a reduction that failed. Tracing wraps and unwraps per run, so it costs nothing when off; what is traced executes for real, writes included, exactly like a run.
The entries below reproduce the source signatures and docstrings.
TraceEvent
class TraceEvent:One step: depth is the nesting level, kind is call or exit, term is what reduced, answer carries the exit's result and stays None on a call.
Trace
class Trace(list):The events, and which bound stopped the recording early.
A list, because that is what a trace IS and every consumer wants to iterate it, index it and take its length.
stoppedis the one thing a plain list cannot say, and it has to be said: the honest answer to "trace this if it is cheap" is a prefix that admits to being one.It names the bound rather than raising a flag because the five bounds have five remedies, and a caller told only that something cut the trace acts on the wrong one: raising
max_eventsafterLimit.memorystopped a trace returns the same prefix again, and raising it afterLimit.inferencesruns the same program into the same wall.truncatedstays as the yes-or-no reading of the same fact.
Trace.truncated
def truncated(self) -> bool:Whether these events are a prefix, whichever bound cut them.
trace
def trace(
space,
source: Atom | str,
max_events: int | None = None,
*,
filter: Symbol | str | Iterable[Symbol | str] | None = None,
timeout: float | None = None,
inferences: int | None = None,
) -> Trace:Run a term, or source, in this space under the engine's reduction trace.
filter selects exact function names before recording; None selects all and an empty iterable selects none. Excluded calls still contribute depth and execute normally, including their writes.
max_events bounds the RECORDING. timeout, inferences and stack bound the RUN, the same triple every evaluating door takes and the same scoped
m.limits()default behind them. The bounds are independent because they stop different things: a program can retire millions of inferences inside a handful of recorded events, and through 0.7.1 this door passed no limits at all, sowith m.limits(inferences=100)let a traced program run 209,322 of them to completion .Whichever one stops it, the events already recorded are ANSWERED and
stoppednames the bound. Discarding them was the whole shape 0.7.0 removed for the recording bound and the run bounds still had: measured 2026-09-04 on 06-peano.metta's own head, a 2,000,000-inference limit took a 10,000-event trace to an InferenceLimitError and nothing else, and the renderer reading it drew 4 frames where the events give 302.