Skip to content

docs(presence): 90s TTL is documented as 3× a 30s heartbeat, but the agent harness heartbeats at 60s (real margin 1.5×) #3795

Description

@tlongwell-block

Summary

Three places document the presence TTL as "3× the 30s heartbeat." The agent harness publishes its presence heartbeat every 60s, so for agents the real flap margin is 1.5×, not 3× — one missed heartbeat leaves 30s of slack, two are fatal. The desktop client does heartbeat at 30s, so the comments are correct for one publisher and wrong for the other, and a fourth doc comment states the TTL itself incorrectly.

Found while truth-auditing presence claims for the remote-agents work (docs/remote-agents.md, PR #3748). This is a comment/doc-accuracy bug with a real behavioral question behind it — not a functional break — but the wrong margin is the kind of number someone sizes a timeout against later.

All line numbers below are at b4f4ed1a67ce340d9709ca32f3b47dd09cd25dea on origin/main's history, verified with git rev-parse HEAD in the checkout that produced them.

The actual values

What Where Value
Redis TTL crates/buzz-pubsub/src/presence.rs:16 PRESENCE_TTL_SECS = 90
Agent heartbeat crates/buzz-acp/src/lib.rs:1583 Duration::from_secs(60)
Desktop heartbeat desktop/src/features/presence/hooks.ts:18 PRESENCE_HEARTBEAT_INTERVAL_MS = 30_000

So the same 90s lease is renewed by two publishers at different rates. 90/30 = 3× for desktop; 90/60 = 1.5× for agents.

Four wrong statements

  1. crates/buzz-pubsub/src/presence.rs:4-5 — "TTL is 3x the 30s heartbeat interval so a single missed heartbeat doesn't cause presence flap."
  2. crates/buzz-pubsub/src/presence.rs:15 — "3x the 30s heartbeat — single missed heartbeat won't cause presence flap."
  3. ARCHITECTURE.md:450 — "90-second TTL (3× the 30-second heartbeat interval). Single missed heartbeat does not cause presence flap."
  4. crates/buzz-pubsub/src/lib.rs:331 — "Set presence with 60s TTL. Call on connect and every 30s heartbeat." The TTL is 90s, not 60s. This one is wrong about the constant it delegates to, in the direction that makes the margin look worse than it is — likely a stale survivor of an earlier pair of numbers.

Note that 1–3 are still literally true of the desktop client. The claim they make that fails is the safety conclusion — "a single missed heartbeat won't cause flap" is a promise about all presence publishers, and it is 30s of slack away from being true for agents.

Why the margin matters, not just the comment

For an agent: last successful publish at t=0, next attempt at t=60. If that publish fails or is delayed past t=90 — relay reconnect with backoff (ARCHITECTURE.md:448: 1s→30s exponential, resets only on clean stream end), rate-limit skip, a slow SET — presence expires and the agent goes dark while alive. The retry isn't until t=120.

The harness heartbeat also fires from the main select! loop (crates/buzz-acp/src/lib.rs:2301-2320), and interval_at starts the first tick at now + 60s rather than immediately; startup is covered separately by the eager publish at :1510, so this is about steady state, not boot.

I did not find field reports of spurious agent-offline flaps, and I did not go looking in logs — so treat the impact as reasoned, not observed. #877 ("fix(presence): clear on disconnect, fix heartbeat/TTL", CHANGELOG.md:1198) is prior art on this exact pair of numbers and probably worth reading before choosing.

The decision this needs (why I'm filing rather than fixing)

Which number is wrong is a product/ops ruling, not a comment edit:

  • (a) Comments were right, the interval drifted. Take the agent heartbeat to 30s, restoring a uniform 3× margin. Cost: 2× presence publish volume from agents.
  • (b) 60s is intentional (agents are chattier per-publish than a browser tab and the relay pays for it). Then fix all four comments to state the real margin per publisher, and say plainly that agents tolerate zero missed heartbeats with 30s to spare.
  • (c) Decouple. Derive the TTL from the publisher's declared interval instead of hardcoding a constant that two callers renew at different rates. Most correct, most work, and probably overkill for two publishers.

My weak preference is (b) plus a one-line note at the constant naming both publishers — the invariant worth documenting is "TTL ≥ 1.5× the slowest publisher's interval," which makes the next divergence visible instead of silent. But I have no evidence about relay publish cost, and whoever owns relay load should overrule me if 30s is cheap.

Whichever lands, the fix should make the two intervals reference each other, because the failure mode here is that they can drift independently and only a comment notices.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions