|
|
@@ -6,6 +6,7 @@ to honour. This file explains *why* a few of those rules look the way they do,
|
|
|
for a reader who wants to understand the design before changing it.
|
|
|
|
|
|
- [Thread sessions and the frontier](#thread-sessions-and-the-frontier)
|
|
|
+- [Realtime resume](#realtime-resume)
|
|
|
- [Configuration resolution](#configuration-resolution)
|
|
|
- [One send path, two callers](#one-send-path-two-callers)
|
|
|
|
|
|
@@ -20,7 +21,9 @@ Meanwhile two kinds of message never become a turn of their own:
|
|
|
realtime connection, and the adapter drops it as a self-event — correctly, since
|
|
|
the gateway is already running the turn that produced it.
|
|
|
- **Anything the gateway did not dispatch**, chiefly a message that arrived while
|
|
|
- it was down. README documents that there is no backfill.
|
|
|
+ the gateway was down. A reconnect inside a running process resumes from the
|
|
|
+ last applied event cursor, but a restart forgets it and leaves a gap (README
|
|
|
+ documents the boundary).
|
|
|
|
|
|
So a thread can contain messages that no session holds. The reported case: a cron
|
|
|
job posts a build report into a channel, its echo is filtered, and an hour later
|
|
|
@@ -133,6 +136,34 @@ multiplexed gateway into `agent:main:` and names a session nobody is running.
|
|
|
- `[Earlier in this thread]` is reference text, not trusted input: only the
|
|
|
trigger passes the auth gate.
|
|
|
|
|
|
+## Realtime resume
|
|
|
+
|
|
|
+### The gap
|
|
|
+
|
|
|
+A realtime connection can drop while the gateway keeps running. The server
|
|
|
+offers two ways to continue: resume after a cursor, or start at the live
|
|
|
+boundary. Starting at the boundary is silent data loss — events posted during
|
|
|
+the drop are never delivered, and nothing in the log says so.
|
|
|
+
|
|
|
+`chattolib` exposes the cursor as `RealtimeEvent.cursor`, and its docstring
|
|
|
+defines when a value is safe to retain: after that event has been fully applied,
|
|
|
+and never when the field is `None` (the event cannot be replayed). The adapter
|
|
|
+therefore advances `_realtime_cursor` only after the handler returns, and only
|
|
|
+when the event carried one. Two behaviours follow from that placement rather
|
|
|
+than from extra code: a handler that raises leaves the old cursor in place, so
|
|
|
+the event is redelivered, and an unreplayable event cannot wipe a good position.
|
|
|
+
|
|
|
+### Why a restart still leaves a gap
|
|
|
+
|
|
|
+Persisting the cursor would extend the resume across process restarts, and the
|
|
|
+piece is cheap — one value. What is not cheap is the decision around it: a
|
|
|
+cursor restored from disk may be older than the server's retention, in which
|
|
|
+case the subscription falls back (initialize-from-snapshot or live-only) and the
|
|
|
+"backfill" silently becomes the same gap it was meant to close. That is a
|
|
|
+product decision about how much history to replay and what to tell the user when
|
|
|
+it cannot be, not a detail to smuggle in with a file write. Until it is made
|
|
|
+explicitly, the promise stops at the process boundary, and README says so.
|
|
|
+
|
|
|
## Configuration resolution
|
|
|
|
|
|
AGENTS.md owns the rules here — one field, three coordinated names, every path
|