Canonization
A concept does not become important because an agent said so in a tool argument. It becomes important because the structure of the session says so, and the structure is measured on a background loop that no agent can call.
This page is the arithmetic behind that sentence. Origin covers where the idea came from. Every constant below is the value the shipped build runs on, and each section links to the file it lives in.
The composite score
Section titled “The composite score”The daemon rescores every concept whenever the graph epoch changes (src/daemon/score.rs):
The four weighted dimensions are session-relative, so the same graph scores identically no matter when you replay it. That is what makes the demo’s output reproducible rather than clock-dependent.
| Term | Meaning | Definition |
|---|---|---|
| recency | , clamped to , in integer milliseconds. A session of at least 10 minutes uses its own span, and those scores match the historical ratio. A younger session scores every concept near 1, so a few milliseconds between derives cannot sweep the dimension. | |
| frequency | ||
| session activity | how many interactions derived this concept, divided by the most-derived concept in the session. The busiest concept scores 1 and a concept nothing derived scores 0. GC’s eviction cut does not read this value; it still divides by the interaction count, which is the scale its threshold was set on. | |
| density | incident edge count, excluding Contradicts, normalized by the most connected concept in the session |
The two additive terms are where the shape of the graph leaks into the number.
pays per incident edge by type, and it pays structure rather than provenance.
| Edge type | Bonus |
|---|---|
Dependency, Causal | 0.02 |
Hierarchical | 0.015 |
Semantic | 0.01 |
CoOccurrence | 0.005 |
Derives, Temporal, Contradicts | 0.0 |
An agent can write a thousand observations and move the score by zero, because the Derives edges those writes create are worth nothing. The sum is capped at 0.25.
is the concept-type offset: for a Constraint, for an Entity or a Logic, for a Resource, and for an Observation.
Every dimension is clamped to before weighting, a non-finite input counts as for that dimension, and the composite is clamped to . The score is finite for every input a client can construct.
The three gates
Section titled “The three gates”That score orders the queue; it is not the promotion. Evaluation runs one hop per cycle, and each stage is a separate predicate against evidence the concept cannot fabricate. Since 0.6.0 no stage selects by score: a concept qualifies on its own evidence or not at all, and the score only decides which qualifying concepts a bounded cycle reaches first.
Stage 1, Candidate
Section titled “Stage 1, Candidate”src/canon/stage1.rs. Under the default Swarm policy a concept becomes a Candidate when both hold:
gc_survived counts the garbage collection sweeps the concept has outlived. distinct is the same count Stage 2 uses: the distinct origin interactions of the concept’s dependents (defined under Stage 3), not counting the interaction the concept itself came from, each dependence edge and each interaction older than canonization_edge_min_age. Two other interactions have to rest on the concept before it is worth evaluating.
There is no peer minimum and no percentile cut. Before 0.6.0 Stage 1 needed 20 non-Canonical concepts in the session and a score strictly above their 90th percentile, which made it a quota: it admitted the top tenth of whatever the session held, and recall traffic moved who that was. Replaying reads on a real store moved the Canonical count by a tenth and changed which named ruling reached Venerable, while the evidence floor gave the same set every time. A young or small session can now qualify on evidence alone, and reading a concept never makes it more promotable.
The composite score orders the admitted set, highest first, so a cycle that admits at most canonization_eval_batch_size concepts takes the strongest first and the rest on the following cycles.
Stage 2, Venerable
Section titled “Stage 2, Venerable”src/canon/stage2.rs. Time and independence, both at once:
distinct counts the distinct origin interactions of the concept’s dependents (defined under Stage 3), not counting the interaction the concept itself came from. span is the temporal reach of those interactions. Edges and interactions younger than canonization_edge_min_age, 60 seconds by default, are excluded from both counts, so a burst of fresh writes cannot inflate a span.
The denominator is the session’s about-time extent, from its earliest to its latest interaction by event time (else creation time), capped at 14 days. A session whose extent is 14 days or less, the demo and the REST API fixture included, is measured against its whole extent, exactly as before. A session with a longer extent needs 4.2 days of spread rather than 30% of everything it has ever recorded, so a ruling cited across a week still passes in a session that is two months old. The extent counts every interaction, including young ones and ones dated after the evaluation clock; the cap bounds how far those can stretch it. The cap is not an expiry: evidence older than 14 days still counts toward the span. A session whose interactions all share one instant counts any support as full coverage, and coverage never exceeds 1. Library callers can change the cap with Config::canonization_coverage_window; see Library API.
One agent writing the same idea five times in one minute produces one distinct source and near-zero coverage. It does not pass.
Stage 3, Canonical
Section titled “Stage 3, Canonical”src/canon/stage3.rs. Consequence. Blast radius is the number of its direct dependents: the concepts that depend on it through a Dependency edge, plus its Hierarchical children:
A dependent is a concept with a Dependency edge into , which is what lambo_record_action’s depends_on writes, or one of ‘s parent_of children. Every dependent counts, including one that also depends on something else. Causal edges, which produces and modifies write, record provenance and count for nothing, and neither do association edges. Lambo reads the stored edges this way when it answers; nothing is migrated.
The comparison is strict. Under either policy, Stage 3 also asks again whether the concept still meets Stage 2: Venerable is not a permanent pass, so a concept whose support has thinned, or was admitted under an older definition of support, waits at Venerable instead of promoting on blast radius alone. A concept that was demoted also stays inside a canonization_repromotion_cooldown of 300 seconds before it can be promoted again, so a flapping concept cannot oscillate its way into permanence. The cooldown is checked first, because it is a field comparison while blast radius is a store round trip.
The Solo policy
Section titled “The Solo policy”promotion_policy = "Solo" is for a single writer, where there are no independent agents to converge. A concept becomes a Solo Candidate by a score over its own evidence instead of Stage 1’s two floors:
sessions counts the 24-hour-separated days on which the concept was derived or something declared that it depends on it, by event time. Since 0.6.0 an action that only produced or modified the concept does not count: a file edited on five days recurred as work, not as a fact anything rests on. distinct is the dependent-interaction count above; it replaced a count of the actions that touched the concept, which rewarded files modified many times and canonized artifacts. is the type’s eviction resistance, from 1.5 for a Constraint to 0.7 for an Observation.
The later bands are 6.0 for Venerable and 10.0 for Canonical, and since 0.6.0 a band is necessary but not sufficient: every Solo hop past Candidate also needs the Stage 2 or Stage 3 verdict that Swarm needs, cooldown included. A Solo session can admit Candidates without GC sweeps, but nothing reaches Canonical without more than five dependents from enough other interactions.
Why a concept is where it is
Section titled “Why a concept is where it is”lambo_inspect, lambo inspect and the portal’s inspect panel explain each concept’s next hop under the live policy: which gates it is checked against, its value and bar for each, and one sentence per gate that fails. The explanation runs the evaluator’s own predicates with the evaluator’s own inputs, the Canonical budget included: a Venerable is not reported eligible while the budget is full, because no Stage 3 window runs then. What it does not predict is timing: whether a cycle reaches an eligible concept this minute is up to the batch size and the round-robin order.
For a whole session at once, lambo canon dry-run runs the same evaluator to its fixed point on an in-memory copy of a store backup and reports, per policy, how many concepts fail each gate, what would be promoted and which concepts are one gate away, without writing anything. lambo canon alias-preview shows what moving old citations of short names onto their rulings would change. See Preview canonization.
Supersession
Section titled “Supersession”Budget demotion is the only way out of Canonical by default. With canonization_supersede = true (a top-level lambo.toml key, off by default; see Configuration) there is a second one, and it follows the same rule as promotion: only evidence counts, never assertion. A Canonical concept is demoted to ordinary memory when a concept that contradicts it (an against write) has earned structural support of its own that is newer than the Canonical concept’s latest support and clears Stage 2’s bars: dependents from at least three other interactions, spread over at least 30% of the capped session extent. Evidence is counted the way contradiction warnings count it: a dependence edge counts once it was written at least 60 seconds earlier, dated by its event time (never later than its write time), and support the contradicting agent wrote for the old belief does not defend it against its own contradiction. Older support for the contradicting concept does not count, and the cut is made before the interactions are counted.
A contradiction with no newer support never demotes anything. The contradicting concept is not promoted by the demotion: it climbs the ladder on its own evidence. The bar is uneven on purpose: the contradicting side must clear Stage 2 again with newer support, while one new piece of support for the old belief, from anyone but the agent that declared the contradiction, puts it ahead again. The demoted concept enters the usual 300-second cooldown, and a concept inside its cooldown cannot supersede another. Stage 3 also holds at Venerable any concept a contradicting concept would supersede at once, whether it was demoted before or never reached Canonical, until it earns newer support. When two concepts contradict each other and each qualifies against the other, only the one with the newest counted support may act, so they cannot hold each other at Venerable forever. Because support the declaring agent writes never counts for the belief it contradicted, a single writer, or agents sharing one id, can never release that belief by their own support: give agents distinct ids before turning supersession on. Supersession runs first in a cycle, so a Canonical slot it frees is spent by the same cycle’s Stage 3. The audit row records the reason (Superseded { by }), recall marks the concept with a ⤓ Superseded at ... line, and lambo inspect names its latest demotion and why.
What recall does with it
Section titled “What recall does with it”src/recall/assemble.rs. Retrieval mixes earned importance with query relevance:
is the BM25 keyword score, or the merged similarity score for a vector hit. It is for a concept that arrived through graph traversal rather than as a query hit.
A recalled concept that was just written to is also flagged as a high-risk modification when it is high-value: Canonical, or with more than five dependents counted live from the graph, the same bar as Stage 3. Since 0.6.0 being Venerable is not enough on its own, because evidence-led admission lets a file with a handful of dependents reach Venerable. The live count has no age floor, unlike Stage 3, so the edge that gives a concept its sixth dependent is itself the fresh write that raises the flag.
That last rule is the one that makes dependency retrieval work. A concept nobody searched for still surfaces if the graph says it is load-bearing, carried entirely by the daemon half of the mix. Canonical concepts are partitioned ahead of everything else regardless of score, and each one returns marked with its blast radius and a warning.
Near-duplicate merging is cosine similarity over the 1024 dimensional embeddings, at semantic_match_threshold:
Two guards refuse a merge whatever the similarity (#193). A new concept never merges into one whose text names other identifiers: the runs of digits and the identifier-shaped tokens (anything with a digit, / or ., such as v0.5.0, src/mcp/server.rs or round3) must be the same, so release v0.5.0 is current and release v0.6.0 is current stay two facts. And it never merges into a concept it names in against, since a claim and the claim it refutes are not one memory. A refused hit leaves the new concept fresh, with its vector, unless another hit at or above the threshold passes both guards. These are the guards canon alias-preview --similar applies to its suggestions.
Without an embedder you keep the whole canonization path and lose this check, which degrades recall over very long sessions as near-duplicates accumulate.
The constants, in one place
Section titled “The constants, in one place”These are library Config settings. Of them, only the [daemon] cadence keys (gc_interval, gc_max_interval_secs, gc_idle_floor, and canonization_eval_interval as canonization_eval_interval_secs) can also be set in lambo.toml; the thresholds cannot. See Configuration for the full set.
| Key | Default | Gate it governs |
|---|---|---|
canonization_min_peer_count | 20 | Inert since 0.6.0: Stage 1 has no peer minimum, and a non-default value is logged as a warning |
canonization_edge_min_age | 60s | Stages 1, 2 and 3 ignore edges and interactions younger than this |
canonization_eval_interval | 60s | How often the evaluation cycle runs |
canonization_eval_batch_size | 50 | Concepts evaluated per cycle |
canonization_repromotion_cooldown | 300s | Stage 3 refusal window after a demotion |
gc_interval | 10000 | Mutations between GC sweeps, which Stage 1 counts survivals of |
gc_max_interval_secs | 86400 | A session still taking writes sweeps at least this often (needs gc_idle_floor mutations since the last sweep) |
gc_idle_floor | 100 | Mutations since the last sweep before the time bound may fire |
semantic_match_threshold | 0.85 | Cosine floor for merging near-duplicates |
The cadence keys change how often the bar is checked. None of them changes where the bar sits.
Verifying it yourself
Section titled “Verifying it yourself”Every promotion writes an audit row, which is what makes this checkable rather than assertable. In the shipped demo, user schema walks Candidate to Venerable to Canonical, with a blast radius of 15 recorded on the promotion and left null on the earlier hops.
You can read those rows from a client that has never heard of Lambo:
- The managed CockroachDB MCP server answering
select_queryagainst a live cluster, scoped to one session - The same events in the database after a live run
- Three independent MCP clients returning that table and agreeing on every field
Evidence and evaluation covers the rest, including the determinism work that makes the demo’s rendered scores byte-identical across runs.