Cost of an update is a function of what depends on it, not of the graph.
O(k) — the size of that
frontier — never O(n), the size of the graph.
refSubscribers: Record<string, Set<string>>
// source path -> Set of dependent target paths
k = |affected dependency frontier| (a mutation's cost)
n = |total graph size| (irrelevant to that cost)
recomputeMode: "eager" | "lazy" (kernel-wide setting)
me.order["="]("total", "price * quantity");
// refSubscribers["order.price"].add("order.total")
// refSubscribers["order.quantity"].add("order.total")
Refs are extracted and resolved when the derivation is declared, not discovered lazily at mutation time. Resolution rule: a dotted label is an absolute path; an undotted label resolves relative to the declaring scope.
queue ← [changed path]
seen ← {}
while queue not empty:
p ← queue.pop()
for target in refSubscribers[p]:
if target in seen: continue
seen.add(target)
recompute(target)
queue.push(target)
A push-BFS over refSubscribers, not single-level fanout:
order.price → order.total → order.withTax propagates in full,
deduplicated against cycles. This is the mechanism an earlier draft of
this page called "bubbling" — a descriptive label, not a term that appears
in the kernel source itself.
me["!"].runtime.setRecomputeMode("lazy");
write(path):
bumpRefVersion(path) // no walk, no recompute
read(path):
if stale(path.refVersions): // ensureTargetFresh — pull, recursive
recompute(path)
return value
Lazy mode skips the BFS entirely on write — it only bumps a version counter. Staleness is resolved on read, by walking downward through the derivation's own inputs rather than outward through subscribers. Eager and lazy are mechanistically different code paths, not the same logic on a delay.
bumpSecretEpoch, which exists in the kernel but
drives only key-derivation cache invalidation on a ~
noise-boundary reset — unrelated to recompute timing. The two are easy to
conflate by name; the code keeps them fully separate.
cost(mutation) = O(k), not O(n)
Holds when dependencies are local. If a single path has 100,000
subscribers, k = 100,000 — the mutation is genuinely
expensive; the index doesn't hide that cost, it just refuses to add graph
size on top of it.
me["!"].explain("order.total")
→ {
path, value, expr,
derivation: { expression, inputs },
meta: { dependsOn, lastComputedAt, k, recomputed, recomputedAt, sourcePath }
}
meta.k and meta.sourcePath are real returned
fields. There is no field literally named "last recompute wave" — that
phrase, where it appears in prior drafts, is a gloss over
recomputedAt and recomputed together, not a
verbatim key. On why this trace matters more than an AI's self-report of
its own reasoning: me.explain() — Why Did You
Say That?
{
label: "walletBalance",
path: "wallet.balance",
value: "●●●●",
origin: "stealth",
masked: true
}
When a dependency input crosses a stealth-scoped path, explain()
masks the value instead of exposing or erroring. Dependency tracking still
functions across the boundary — the structure of the derivation is
visible, the secret value is not.