Case study · HashRelay, built with Continuum

How long should a missed alert wait for you?

HashRelay sends notifications from scripts, CI jobs and AI agents to every device subscribed to a hashtag. When a laptop is offline, its notifications are queued. This case study shows how Continuum turned “how long do we keep them?” into a decision the model, the code and the next agent session all agree on.

The problem

The model, the code and the dashboard disagreed

The model said

Queued deliveries were retained until acknowledged, with no upper limit.

The code did

Expired them after ReplayWindowHours (24 by default), and only when the device reconnected.

The reports showed

Expired deliveries counted as delivered in the activity list, the duplicate check, the notification page and the dashboard.

From the rationale recorded with ADR-001 by the agent that proposed it. The project’s architecture notes also listed offline retention as unresolved.

The decision

ADR-001: offline deliveries are kept for a bounded replay window

The trade-off: a laptop closed overnight should still catch up, but a device that never returns shouldn’t make storage grow forever, and nobody should be told an alert arrived when it didn’t.

Decision record · ADR-001In force
Decision   "A delivery queued for an offline endpoint is retained until it is
            acknowledged or until its replay window (ReplayWindowHours, 24 by
            default) elapses, whichever comes first. An expired delivery is
            recorded as expired and is never counted as delivered."

Alternatives considered
  · Retain until acknowledged, with no bound — storage grows without limit
    for endpoints that never return
  · A per-workspace replay window — more configuration than any current
    workspace needs

Consequences
  · Queued deliveries expire on a schedule, not only when the endpoint reconnects
  · Activity, duplicate and notification views stop counting expired as delivered

Risk
  · A device offline for longer than the window loses those notifications;
    it is told they expired, not that they were delivered

Proposed 5 Nov 2026 by an AI agent (Claude Code) acting for the owner
Accepted by the project owner · rule: a person who did not author it approves
Signed by the Continuum Host · record verified: 19 events, chain intact

Excerpt from architecture/decisions/DECISIONS.md in the HashRelay repository (commit 265bafc), rendered from the signed record by continuum-cli 0.28.3. Identities are omitted.

Model-derived diagram

Where the replay window applies

Continuum Diagram drew this from HashRelay’s compiled model, then rebuilt it as an editable SVG and checked every box and arrow against the model. The requirement that ADR-001 added is highlighted.

Diagram titled 'Offline catch-up has a limit'. A Publisher publishes to RoutingService, which publishes the NotificationAccepted event to DeliveryService. EndpointService writes to EndpointStore, which DeliveryService reads. DeliveryService reaches DesktopAgent and MobileApp in real time, and mobile apps through Firebase Cloud Messaging. A panel lists the HashtagDelivery requirements DeliveryService satisfies: BoundedOfflineRetention (highlighted: offline deliveries have a deployment-set replay window; once it elapses they expire, even without reconnecting, and are never counted as delivered), CatchUpWithoutInterruption, OncePerEndpoint and DeliveryEvidence. Solid arrows are stated in the model; dashed arrows follow from it.

Verification: of 23 claims on the canvas, 19 are declared in the model and 4 are inferred from quoted descriptions (drawn dashed). None is drawn stronger than the model supports. Model attestation sha256:8f88867a….

The 24-hour figure is not in the diagram because the model doesn’t contain it. The model states that the window is bounded, and the decision sets the default. Open full size

What Continuum produced

The model change, its review and its validation

The change

A change file adds the requirement, links the service that satisfies it, and rewrites the catch-up requirement so it no longer promises unbounded retention.

2026-11-04-bounded-offline-retention.adl-changeexcerpt
add into HashtagDelivery {
  requirement BoundedOfflineRetention {
    desc "… replayable for a bounded replay window
      set per deployment. When the window elapses the
      delivery becomes expired, whether or not the
      endpoint ever reconnects. … never counted as
      delivered."
  }
}
link DeliveryService satisfies BoundedOfflineRetention

// Catch-up keeps its meaning but loses the promise
// the system never kept.
modify CatchUpWithoutInterruption description = "… retained
  until acknowledged or until their replay window
  elapses …"

The review

Before the change, the decision check failed on the model. After it, the check passed, and the proof that applying the change produces the new model came back equivalent.

ADR-001 alignment checkse3 → e6
model sha256:c2824b88…
change-required · 2 missing from the model

model sha256:a89061e4…  (after the change)
aligned · all expectations hold
apply(change) → EQUIVALENT

Validation today

Compiled again for this page, from commit 265bafc.

adlc · hash-relay.adl-manifest.json · summary15 Nov 2026
ledger       78 passed, 0 failed, 0 not-evaluated
errors       0
warnings     18 (unrealized-intent)
change-state sha256:a89061e4…f7f

warning adl/architectural/unrealized-intent
  @ Requirement HashtagDelivery.BoundedOfflineRetention
    required: verifies, and it is not supplied

That warning is accurate: tests for this rule exist in the code, but the model doesn’t yet declare which test verifies it.

Either side of the boundary

23 hours: still waiting. 25 hours: expired, and said so.

Two regression tests pin the 24-hour window. They were written before the fix, and the second one failed first: expected 0 delivered, actual 1.

Inside the window

The device is still offline at 23 hours

Given
a device subscribed to #ops is offline, and “Disk almost full” is published
When
the expiry sweep runs 23 hours later
Then
nothing expires. The delivery is still Queued for replay.
Outside the window

The device never returned by 25 hours

Given
the same kind of queued delivery, “Certificate renews tonight”
When
the sweep runs 25 hours later, although the device never reconnected
Then
it is Expired, the activity list shows 0 delivered, and a retried publish reports it neither delivered nor queued

Paraphrased from tests/HashRelay.Server.Tests/RetentionTests.cs. Results were recorded in the decision record as reported, not verified by Continuum: 167 of 167 tests passed at that commit. The tests don’t run the hosted timer and don’t establish production behaviour. HashRelay’s Continuum acceptance package has no scenario for this requirement yet.

The next session

Six days later, the decision held

On 11 November an agent drafted macOS and Linux support in a new session. Its change file names the model state ADR-001 produced as its base. It says the proposal leaves replay alone: ack and replay stay as CatchUpWithoutInterruption/BoundedOfflineRetention state them. It also marks itself not approved, not adopted, so reviewers can see what is decided and what is proposed.

What Continuum contributed, and what it didn’t

Part of the workContributed by ContinuumNot established by Continuum
Spotting the mismatchA model with an exact statement to compare against the codeThe comparison itself. The agent read the code.
DecidingA signed decision record with alternatives, risk, separation of duties and model expectationsWhether 24 hours is the right number for users
ModellingA change file that compiles, an alignment check that went from failing to passing, and proof the change appliesThat the model covers every replay case
BuildingA place to record test evidence and an honest label for itThat the code conforms. Tests are reported, and the hosted timer was not exercised.
ContinuingA pinned model state the next session built on, and a diagram drawn from itThat every future change will respect the decision

Use the same process on your system

The walkthrough shows each step and what a team does at each one. Launch updates tell you when you can try it.