Summary
Heartbeat-style $meshState documents (lease renewals, health pings, presence) accumulate Automerge history unboundedly. Today every consumer rolls its own change-count threshold plus compaction call plus per-key cooldown loop. A declarative per-wrapper policy hint, paired with a Repo-level interval that drives it, would let consumers state intent once instead of rebuilding the loop.
Spun out of #115 (Q5). The owner's position there: worth doing, but wanted to see one more consumer of the pattern before committing to the option shape.
Proposed shape
A compactionPolicy option on each $mesh* primitive:
type CompactionPolicy = {
kind: "heartbeat" | "tombstone-filter" | "manual";
threshold?: number; // change-count trigger (uses the changeCount getter from #115)
cooldownMs?: number; // minimum gap between compactions of the same key
};
heartbeat — snapshot the wrapper's current materialised state into a fresh doc (no history). For docs where only the current state matters.
tombstone-filter — entries-map filter (the mesh:devices shape).
manual — opt out; no automatic compaction.
Plus a Repo-level interval that, on each tick, fires the appropriate primitive against every wrapper that declared a non-manual policy whose changeCount exceeds threshold and whose last compaction is older than cooldownMs.
The per-key cooldown and best-effort (failures log, never crash the tick) shape is the one fairfox already runs in its relay heartbeat.
Dependencies
Open design questions
- Who is allowed to compact (signing / access)? Heartbeat snapshots reseed a doc — the policy needs an eligibility answer.
- Option shape: confirm against the second real consumer before locking the
CompactionPolicy type.
- Whether
tombstone-filter needs a consumer-supplied predicate or can be expressed declaratively.
Acceptance criteria
Tracking
Follow-up to #115. Depends on the design questions above being settled — owner wants a second concrete consumer of the pattern first.
Summary
Heartbeat-style
$meshStatedocuments (lease renewals, health pings, presence) accumulate Automerge history unboundedly. Today every consumer rolls its own change-count threshold plus compaction call plus per-key cooldown loop. A declarative per-wrapper policy hint, paired with a Repo-level interval that drives it, would let consumers state intent once instead of rebuilding the loop.Spun out of #115 (Q5). The owner's position there: worth doing, but wanted to see one more consumer of the pattern before committing to the option shape.
Proposed shape
A
compactionPolicyoption on each$mesh*primitive:heartbeat— snapshot the wrapper's current materialised state into a fresh doc (no history). For docs where only the current state matters.tombstone-filter— entries-map filter (themesh:devicesshape).manual— opt out; no automatic compaction.Plus a Repo-level interval that, on each tick, fires the appropriate primitive against every wrapper that declared a non-
manualpolicy whosechangeCountexceedsthresholdand whose last compaction is older thancooldownMs.The per-key cooldown and best-effort (failures log, never crash the tick) shape is the one fairfox already runs in its relay heartbeat.
Dependencies
changeCount(shipped in Heartbeat $meshState docs grow unbounded — no built-in compaction, mobile Safari OOMs on first sync #115, v0.68.0) as the threshold input.heartbeatkind composes with the redirect plumbing (registerRedirectDetector) so in-process wrappers rebind transparently after a snapshot.Open design questions
CompactionPolicytype.tombstone-filterneeds a consumer-supplied predicate or can be expressed declaratively.Acceptance criteria
compactionPolicyoption accepted on$meshState/$meshText/$meshCounter/$meshList.manualpolicies pastthreshold, respectingcooldownMs.manual(and the absence of the option) means no automatic compaction.Tracking
Follow-up to #115. Depends on the design questions above being settled — owner wants a second concrete consumer of the pattern first.