apply_executor_ager, introvertiva_propose, introvertiva_apply) is gated by the user-interaction signal: a Metnos on holiday no longer deprecates executors for calendar inactivity. The introvertiva has deterministic quality filters: vocab validator on proposed_name, skip of flow args (entries, from_step, results) and template values {{stepN.field}}, min_uses threshold at 10. Proposal volume at 6 with high expected quality.
mnestome — the emergent graph of all mnestsThis document defines the mnestome: the data structure that holds the set of all active mnests and governs their lifecycle. Technically it is a weighted directed graph persisted in SQLite; ontologically it is Metnos's relational memory — what it remembers having done together.
The document covers:
It does not cover, and defers to other docs:
The mnestome is a weighted directed graph in which:
The mnestome emerges. No one designs it a priori: it is born empty at first boot and forms turn after turn, while the gateway observes which executors pass output to which others. The shapes it takes reflect how the system is used: an «invoice archiving» user's mnestome has different clusters than a «daily news roundup» user's, even when the seed of executors is the same.
The word «memory» in an AI agent has too many meanings. It is worth saying clearly what the mnestome is not:
| What the mnestome is NOT | Where that concept lives, then |
|---|---|
| The LLM's conversational memory (recent turns, in-token context). | In the working memory of the execution trace. |
| The episodic memory of past conversations (what Roberto said yesterday). | In the episodic memory, indexed by sender. In the episodic memory store. |
| The semantic memory of facts about Roberto and his world («the dog's name is Fido»). | In the semantic memory, extracted via reflection and approved by the user. |
| The audit log of completed actions. | In workspace/.audit/, append-only, JSONL. |
| The user's file workspace (notes, invoices, photos). | In workspace/, managed separately. |
| The definition of capabilities (the individual executors). | In workspace/executors/, file by file. |
The mnestome is the observed relation between executors: who worked with whom, how often, how recently. Nothing more.
To understand what a mnestome really does, it helps to stop thinking of it as a SQLite table for a moment and start thinking of it as a living environment: a broth in which small particles float, meet, attract one another, combine. Out of that dynamic, more complex structures emerge through progressive aggregation — a process that, every so often, recalls what we see in other natural systems made of many elements interacting with each other.
What follows is a metaphor, not an identity. I use it with restraint: it is meant to show three things the mnestome actually does — aggregate what is similar, recognise what is missing, let new structures arise when recognition meets the right material — without claiming a literal parallel that is not there.
At first the broth is empty. Then the particles begin to appear: small traces of life that the system collects while the user uses it. They come in a few species, each with its shape and role:
Each particle has a story: a mnest is the memory that “A served B”; an executor is a small programme that knows how to do one specific thing; a fast-path is a shortcut the system has learned (the canonical request “X is solved like this”); a proto-mnest is a mnest half-done: real src, dst desired but not yet existing. You can see immediately in the drawing that the proto-mnest has a different shape, a sort of open receptor: there is an empty slot waiting to be filled.
In the broth, nothing is truly random. Particles attract each other
based on semantic similarity, measured with the BGE-M3
multilingual model. When the user repeatedly does the same thing
— three “download this page” requests that always
turn into get_urls followed by
describe_entries — the mnests observed across
the three turns refer to the same pair of executors. Those mnests
form a cross-turn cluster: evidence that “this
pipeline works”.
At this point something interesting happens: the cluster does not remain a blurry cloud, it crystallises. It becomes a fastpath, a preformed path that describes the sequence in a compact, directly executable form. From that moment on, the PLANNER no longer needs to call the language model to re-plan the pipeline from scratch: it reads the fastpath and uses it directly. The figure below shows the four phases — observation, cluster, crystallisation, reuse — in sequence.
The fastpath that emerges is not an abstraction: it is precisely that concrete sequence, memoised, ready to use. The saving is tangible — the call to the LLM planner (about twelve seconds) is skipped entirely, and the user gets the answer in under a second. The cluster that produced it stays in the mnestome as a trace of how the fastpath was reached; the fastpath itself lives in its own separate registry (see fast_path.html).
Sometimes, as the system works, something more peculiar happens: the
planner would like to call an executor that does not exist.
Maybe the user asked something new, and in the ideal chain the final
step should have been a compose_report that has never
been written. What gets recorded, at that moment, is a
proto-mnest: a trace with a known src and
a dst that is only a name, not yet a programme.
We can see it as a receptor: a shape with an empty gap where the dst would fit. The gap is not a graphic detail, it is the desired signature — it declares what would be needed to close the shape. The receptor waits: it stays stable as long as no one fills it, and becomes more visible as it shows up in the same way, turn after turn.
Somewhere in the broth, the introspective functions work: the silent crafts of synt in its introvertive mode and of the introvertiva module. They have no final function of their own; they exist to make other things happen. Their hallmark is the ability to recognise receptors: to read the open gap and understand which dst would close it.
When an introspective function meets a proto-mnest that has been seen more than once (the threshold is three recurring observations, ), it approaches, analyses it, reads the desired signature, and proposes: it emits a candidate for synthesis. The proposal says: “this receptor exists, it recurs, and here is a possible dst that would close it”.
Another interesting phenomenon happens in the broth: two fast-paths that live adjacent and often appear in the same turn end up hooking together to form a longer fast-path.
Consider a typical turn: “download me this page” triggers an L1 single-tool fast-path; immediately after, “tell me what it says” triggers another L1 fast-path. The system observes the succession, records the sequence, and when it repeats three times promotes it to an L2 multi-step fast-path. From that moment on the pair «download + describe» is a single recognised sequence.
The sequence does not stop at two. Three adjacent fast-paths can
form a longer chain, and so on. The system keeps the whole chain,
measures how often it repeats, and beyond a threshold (50 uses by
default) a nightly job called multi_tool_promote
presents the pipeline to synt as a proto-mnest: “here is a
sequence worth unifying into a single executor”.
At this point it is worth pausing to observe that synt receives proto-mnests from two different paths, independent of one another, converging at the same point.
The direct path: the introspective functions observe the mnestome and identify the receptors that recur — those proto-mnests that present themselves in the same way turn after turn, threshold three recurring observations. When they find one stable enough, they propose directly an executor that would close the gap. There is no need to go through a fast-path: the analysis of the single receptor is enough. This is the classic case, and historically the first that synt learned to handle.
The indirect path: an L2 fast-path pipeline that
has accumulated enough uses (fifty by default) is promoted by the
nightly job multi_tool_promote to a proto-mnest. This
time the proto-mnest is not born from a failed request but from a
sequence that worked many times: the desired signature describes a
unified executor that wraps the whole chain of calls.
The two paths lead to the same entry point. Synt receives the proto-mnest, reads the signature, and translates it into a complete structure: it runs its five stages (naming, signature, tests, description, code), produces a new executor, signs it with ed25519, registers it in the catalog. The receptor is no longer open: the dst exists, the proto-mnest is promoted to an active mnest. The new capability is unique, reusable, and — whether it comes from the direct analysis of a receptor or from the aggregation of a pipeline — enters the executor pool like all the others.
describe_urls executor, the aggregated pipeline
[get_urls, describe_entries] that originated it is
immediately demoted: its shape dissolves back into the
broth, and every new request goes directly through the monolithic
executor.
From candidate fast-path to non-creation: symmetrically, if
the system is about to record a new fast-path and discovers that the
executor that would do the same thing already exists in the catalog,
the fast-path is not created: there is nothing to memoise if the
unified capability is already available.
The runtime applies the rule on the fly in the matcher (auto-demote)
and in the recording (silent skip); the planner knows it from its
prompt and never emits the double sequence when the unified executor
is in the pool. The form has priority over the shortcut, both
incoming and outgoing.
What emerges, seen from a distance, is a system in dynamic equilibrium: closed but alive, where every component takes part in a cycle. Particles are born from the observation of the turn, they attract by similarity, form clusters, some clusters become fast-paths, some fast-paths aggregate into pipelines, mature pipelines become proto-mnests that ask for synthesis, synthesis produces new executors, the new executors generate further mnests and everything starts over.
At the same time there is a cycle of forgetting: the nightly ager (see chapter 6) decays the unused particles, reduces their weight, moves them to decaying state, eventually archives them. Things that are no longer used disappear. The broth cleans itself.
All of this happens inside a single SQLite file of a few MB. The metaphor of the broth is not a teaching trick: it describes faithfully enough what the code does. Understanding this level makes the chapters that follow — the data schema, the operations, the ager — obvious: they read like the technical explanation of a process you have already seen in motion.
The mnestome lives in workspace/.mnestoma/mnest.sqlite, a
single SQLite file. The choice of SQLite is deliberate: zero services
to start, zero external dependencies, trivial backup (file copy),
proven reliability.
executors
A lightweight replica of the manifest, in fast-read mode for gateway
queries. The canonical manifest always remains
workspace/executors/<name>/manifest.toml; this table
is an index.
CREATE TABLE executors (
name TEXT NOT NULL,
version TEXT NOT NULL,
state TEXT NOT NULL, -- seed | active | quarantine | archived
loaded_at TEXT NOT NULL, -- ISO 8601
manifest_hash TEXT NOT NULL, -- blake3
PRIMARY KEY (name, version)
);
CREATE INDEX idx_executors_state ON executors(state);
mnestsCREATE TABLE mnests (
id TEXT PRIMARY KEY, -- ULID
src_executor TEXT NOT NULL,
src_version TEXT NOT NULL,
dst_executor TEXT NOT NULL, -- canonical name OR desired name (proto)
dst_version TEXT, -- NULL for proto
weight REAL NOT NULL CHECK (weight BETWEEN 0 AND 1),
uses INTEGER NOT NULL CHECK (uses >= 1),
ts_first TEXT NOT NULL,
ts_last TEXT NOT NULL,
decay_lambda REAL NOT NULL DEFAULT 0.018,
state TEXT NOT NULL, -- proto | active | decaying | superseded
tags TEXT, -- JSON array
desired_sig TEXT, -- JSON, only for proto
CHECK (ts_last >= ts_first),
UNIQUE (src_executor, src_version, dst_executor, dst_version, state)
);
CREATE INDEX idx_mnests_dst ON mnests(dst_executor);
CREATE INDEX idx_mnests_src ON mnests(src_executor);
CREATE INDEX idx_mnests_weight ON mnests(weight DESC);
CREATE INDEX idx_mnests_state ON mnests(state);
eventsAppend-only, records every reinforcement or decay applied. Allows the weight trajectory of a mnest to be reconstructed and to be recomputed later if the formula changes.
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mnest_id TEXT NOT NULL,
ts TEXT NOT NULL,
kind TEXT NOT NULL, -- reinforce | decay | state_change
delta REAL, -- weight delta (NULL for state_change)
new_state TEXT, -- only for state_change
reason TEXT, -- e.g. "ager nightly", "synt approved"
FOREIGN KEY (mnest_id) REFERENCES mnests(id)
);
CREATE INDEX idx_events_mnest ON events(mnest_id);
CREATE INDEX idx_events_ts ON events(ts);
canonical_query_log
A log of user requests in lemma form, used by the
introvertive fast-path (L1 single-tool,
). Every time the planner decides the first executor of a
turn, a row is inserted or reinforced here: canonical query (lowercase
condensed form), chosen tool, observed argument values. After three
consolidated passes, the canonical_matcher uses BGE-M3
cosine to match new requests against this log and skip the planner.
CREATE TABLE canonical_query_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
canonical_query TEXT NOT NULL,
tool_name TEXT NOT NULL,
args_shape TEXT NOT NULL, -- placeholder template (back-compat)
args_observed TEXT, -- ACTUAL argument values
uses INTEGER NOT NULL DEFAULT 1,
ok_count INTEGER NOT NULL DEFAULT 0,
fail_count INTEGER NOT NULL DEFAULT 0,
ts_first TEXT NOT NULL,
ts_last TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'candidate',
UNIQUE(canonical_query, tool_name, args_shape)
);
The args_observed column (added via
idempotent migration) stores the concrete argument values observed
on the first planner pass. When a new request matches the row, the
argument extractor
(args_extractor)
reuses them directly, avoiding re-running regex extraction or
calling an LLM. The analogous multi-step sequences live in a separate
sqlite (multi_tool_paths.sqlite); see
fast_path.html for the full picture.
v_mnestome
The «live» graph for read queries. Excludes
superseded and shows active + proto:
CREATE VIEW v_mnestome AS
SELECT id, src_executor, src_version, dst_executor, dst_version,
weight, uses, ts_last, state, tags, desired_sig
FROM mnests
WHERE state IN ('active', 'proto');
record_passingThe only write operation during a turn. Atomic per pair. Pseudocode:
def record_passing(src, dst, turn_ctx) -> mnest_id:
with conn.transaction:
if dst.exists:
row = SELECT * FROM mnests
WHERE src_executor = src.name AND src_version = src.version
AND dst_executor = dst.name AND dst_version = dst.version
AND state = 'active'
if row:
w = decay(row.weight, now - row.ts_last, row.decay_lambda) + REINFORCE_DELTA
UPDATE mnests SET weight = clamp01(w), uses = uses + 1, ts_last = now
INSERT INTO events (mnest_id, ts, kind, delta, reason)
VALUES (row.id, now, 'reinforce', REINFORCE_DELTA, turn_ctx.id)
return row.id
else:
id = ulid
INSERT INTO mnests (id, src_*, dst_*, weight, uses, ts_first, ts_last, state, tags)
VALUES (id,..., BOOTSTRAP_WEIGHT, 1, now, now, 'active', tags)
INSERT INTO events (mnest_id, ts, kind, delta, reason)
VALUES (id, now, 'reinforce', BOOTSTRAP_WEIGHT, turn_ctx.id)
return id
else:
# proto-mnest
id = ulid
desired = build_desired_signature(turn_ctx)
INSERT INTO mnests (..., dst_executor=dst.desired_name, dst_version=NULL,..., state='proto', desired_sig=desired)
return id
| Query | Use |
|---|---|
top_k_outgoing(executor, k) | Given an executor, returns the k heaviest outgoing mnests. |
top_k_incoming(executor, k) | Given an executor, returns the k heaviest incoming mnests. |
walk(start, max_depth) | Weighted BFS from the starting node, up to a maximum depth. Used by the gateway to build the execution context. |
recurring_protos(min_uses, since) | List of proto-mnests that have crossed a recurrence threshold. Trigger of the synt. |
by_tag(tag) | All mnests with a given tag. Used by observability and the ager for clustering. |
decaying | Mnests in decaying state. Used by the ager for archival proposals. |
State transitions are made by specific components (ager, synt, gateway
in case of executor quarantine). All are accompanied by an
events row with kind = 'state_change' and a
mandatory reason; this is the principle of reversibility
(ch. 14 of the Architecture): you do not change state without a trace
of the why.
The ager is the process that applies decay and signals the state transitions that pure time requires. It runs once a day, in a night window (default: 04:00 local time), in a long but low-impact transaction:
active mnest, compute weight_new = weight_old · exp(-λ · Δt) where Δt is the time elapsed since the last event. Update the weight and write a decay-kind events row.decay weight threshold moves to decaying; it is signalled in the proposals channel to the gateway.decaying mnest below archive weight with ts_last > 90 days enters the list of archival proposals shown to Roberto.min proto weight is deleted (anti-noise).uses >= SYNTH_TRIGGER_USES and weight >= SYNTH_TRIGGER_WEIGHT; signal them to the synt as candidates for synthesis.workspace/.mnestoma/snapshots/YYYY-MM.sqlite.The ager is a pure example of a component that only does maintenance: it does not modify the structure of mnests, does not create executors, does not speak to the user. It proposes; the deciders are synt + Roberto, or the archival policy that the gateway applies in the next turn.
On the first of the month (UTC), the ager produces a snapshot of the live file:
workspace/.mnestoma/snapshots/2026-04.sqlite # current month
workspace/.mnestoma/snapshots/2026-03.sqlite.zst # older, compressed
workspace/.mnestoma/snapshots/2026-02.sqlite.zst...
The snapshot serves three purposes: rollback in case of corruption of
the live file, historical analysis of the graph, trace for whoever
wants to understand how Metnos «formed» over time.
Snapshots older than 12 months are .zst-compressed but
never deleted: history is a first-rate datum.
The live file would stay up even when full, but for performance and cleanliness a periodic pruning helps:
superseded and archived mnests are moved to a mnests_archive table after 90 days (still in the same SQLite file);events table is partitioned by year (tables events_2026, events_2027,...). Only the current year is in the live schema; previous years move to a second SQLite file events_history.sqlite;VACUUM runs monthly right after the snapshot.
When an executor moves to archived, its mnests move to
superseded with reason = 'executor archived';
they remain readable, are excluded from the v_mnestome
view, and after 90 days move to the archive table.
The term has two normative forms:
The double form is intentional and to be preserved. Translating mnestoma into mnestome in English gives the English reader the right reflex (the biological suffix evokes an emergent graph, like the microbiome). Translating mnestome into mnestoma in Italian follows the phonetic rule that avoids suffixes felt as incongruous (mnestome in Italian would sound like a verbal imperative, «you, memorise!»).
At first boot, the mnestome is empty. The question «then where do we start?» has three concrete answers:
state = 'seed' to distinguish them and weight BOOTSTRAP_WEIGHT (see mnest.html ch. 6).
The user can inspect the mnestome from the CLI (via the
Metnos mnestome command):
Metnos mnestome list # all active mnests
Metnos mnestome list --tag invoice # filtered by tag
Metnos mnestome top 20 # the 20 heaviest
Metnos mnestome graph executor read_files # neighbourhood of an executor
Metnos mnestome proto # current proto-mnests
Metnos mnestome history mnest_01HW... # event history of a mnest
Metnos mnestome snapshot --month 2026-03 # opens the monthly snapshot
The web dashboard has a graphical view of the same content, with filters and drill-down. The transparency rule: the user can always know what Metnos thinks it knows.
Metnos — mnestome microdesign
New canonical doc (concept introduced by ).