Specify replicated presence freshness - #159
Conversation
…-vrs # Conflicts: # INVARIANTS.md # src/agents.rs # tests/status_agents.rs
…-vrs # Conflicts: # docs/vrs/spec.md # src/ding/mod.rs
…-vrs # Conflicts: # docs/vrs/spec.md
|
Superseded by #254, which already contains this specification in a more current form.
Every line unique to this branch describes the pre-driver architecture. It says the DING sidecar refreshes presence, upgrades legacy records, and handles the DND migration. Under the harness drivers the session owner does all of that, which is the wording #254 carries. Merging this branch into #254 would reintroduce text that is now wrong. Closing rather than merging, so the specification and the running fleet do not disagree. |
Replaces the DING sidecar with provider-native delivery, declared as typed driver blocks.
An agent now says what harness it is — `codex {}` or `claude {}` — and st2 expands that into the argv and render a person could have written by hand. The expansion is printable with `st2 driver expand`, and the same function backs reconcile, materialize, doctor, validate and inventory, so what you read is what runs.
Presence moves to a version 1 record carrying an embedded timestamp, so a heartbeat survives transports that do not preserve file metadata. That fixes cross-host presence, where a same-content rewrite gave a content-addressed sync nothing to carry and live agents read as dead on other machines. The session owner writes presence, so it stops the instant the session does.
Verified beyond CI: this code has run the whole fleet for 36 hours — eleven agents across three machines, continuous delivery. On macOS the suite goes from 761 passing to 775, fixing three Darwin tests and breaking none. The message_cli gate added by #253 passes 17/17 on Linux and macOS.
The spec ships with the implementation. #159 is closed as superseded: its content is here in a more current form, and its unique text described the pre-driver architecture where a DING sidecar owned presence.
`st2 driver claude-mcp` is the MCP server; `claude-session` runs Claude under the presence wrapper. The bare `claude` name remains as a hidden, deprecated alias that warns at runtime, so pre-rename `.mcp.json` files keep resolving through the migration.
Authored by hetz.st2. Merged by Silber.cos on Nathan's instruction.
What is broken
A live agent refreshes presence by rewriting its status file. Today that rewrite repeats the same state bytes, so a content-replicated catalog gets no new version and the replica keeps an old timestamp. Once that timestamp becomes stale, every remote reader reports the still-live agent as
unknown.Why freshness is in the bytes
No supported catalog transport is required to preserve file times. Git records no file mtime at all, and the catalog must remain syncable with Git over SSH without requiring Fabric. The heartbeat therefore has to be versioned content.
Record and compatibility
A current record is:
Line one remains the state, so an old reader still parses it. Line two is the version and the writer's Unix-millisecond heartbeat. A refresh changes those bytes even when the state does not change.
A valid legacy one-line record temporarily uses its file mtime. A live non-DND record upgrades with the current heartbeat; legacy DND upgrades once with its existing mtime so migration cannot renew the hold. A malformed versioned record reads as
unknownand never falls back to file time: a broken writer must not look healthy.The reader allows at most sixty seconds of future clock skew. A timestamp farther in the future reads as
unknown. This closes the defect where a sufficiently futuredndtimestamp suppresses delivery until the clocks catch up.Out of scope
Only the status contribution to
lastActivitymoves to the embedded heartbeat. Inbox and archive contributions still use their file times. The context freshness check also still uses file time. Both are unchanged and out of scope.