Admitting one turn from an at-least-once queue
Why a mention needs redelivery to survive a crash, and how keeping the dedup marker in the commit that admits the turn stops a teammate answering twice.

When someone mentions an AI teammate in a chat channel, two failures are easy to picture, and they pull in opposite directions. A process dies between the message arriving and the work starting, and the teammate never answers. Or the work is delivered again after the first delivery went through, and the teammate answers twice while you pay for the model twice. The delivery behavior that protects against the first failure is the one that causes the second, so neither can be tuned away at the queue. In the collaboration application I consolidated in the summer of 2026, I kept the redelivery and made the application itself responsible for recognizing work it had already taken on.
The relay, its protocol and its client came from Block’s open-source Buzz, a Nostr relay where every message, reaction and workflow step is a signed event in one log. I led the integration around it, which began as three separately deployed applications: a relay, an agent runtime and an AI gateway, wired together through private queues and seam tokens.
On Ferrite, those three became modules of one application, and most of the queues went away. The command queue for personas, as the system calls these teammates, became a function call, and its polled result store went with it. The request and reply queues to the model provider, with their correlation polling and reply cache, became a plain async function. One queue stayed, the one carrying a turn request from an admitted event to the agent runtime, because at-least-once delivery is what lets a mention survive a crash. Everything else here follows from keeping that queue, since it moves the problem of duplicates to the receiving end.
Signed events make the receiving end easier than it would otherwise be. An event’s id is a hash over its content, so a redelivered event arrives with the identity it had the first time, and nobody has to mint or pass along a token to make that true. What remains is knowing where to ask whether that id has been seen and what to do with the answer, and in this design the question gets asked in two places.
The first is the relay. Every write, from a person’s client or from a persona, went through the same ingest function and its membership, moderation and write-authorization checks. It then tried to admit the event with a create-if-absent keyed by its id. The interesting case is the create that finds the id already there. Reporting success and stopping is the obvious response, and it would be wrong, because the earlier attempt may have written its marker and died before finishing. A duplicate therefore resumed the unfinished admission steps instead of treating the marker alone as proof that everything had happened.
That rule has a consequence further along. Finishing those steps includes handing the turn to the agent runtime, so one event can legitimately enqueue its turn more than once, quite apart from anything the queue does by itself. The relay dispatches at least once; the session has to admit the resulting turn once.
The second place is the session. The turn request carried the triggering event’s id as its message id, and admitting the message read the session and a dedup marker in the same consistent snapshot, then wrote a guarded commit of three entries. If two deliveries raced, one lost the guard, retried, and found the marker. Keeping the marker inside the commit that admits the message is what makes its answer worth trusting. Written separately, a crash between the two writes leaves either a message with no marker, which runs again, or a marker with no message, which never runs and brings back the silent teammate from the first paragraph. Skipping on a duplicate was also stated rather than assumed: a channel trigger skipped, whereas a delegated call was told to run anyway. And one turn here means one per persona, so a message naming two of them reached both, each answering under its own key.
The same idea handled what came back out. A model can retry a tool call and a redelivered turn can repeat a post, so each persona command carried a correlation id, and its first execution reserved the event’s timestamp under that id, again create-if-absent. A retry, or the loser of a race, read the reserved value and rebuilt a byte-identical event, which deterministic signing makes possible, and the relay’s admission marker absorbed it as the duplicate it was. What I like about this retry path is how little has to be remembered: one timestamp. The rebuilt write still passed through the same ingest checks as the original.
Testing this means showing that something didn’t happen, which is easy to do badly. The capstone test ran a mention against a scripted model and checked for one model call and one signed, threaded answer. It then waited for the queue to go quiet, with acknowledged deliveries equal to published ones, published the trigger again by hand, waited for quiet again, and checked that the model-call and answer counts had not moved. The waiting matters as much as the counts. Without evidence that the queue had consumed the replay, an unchanged count would look exactly like a replay still sitting in the queue.
Correct counts don’t settle everything a turn can get wrong. During a live run at the end of July a persona posted a haiku and then, when the turn failed afterwards, posted a turn error underneath it: one turn, properly admitted, and two messages disagreeing about how it had gone. The fix threaded the number of posts made through the turn’s result, so the failure path knew the persona had already spoken, and a test carried the rule in its name: a turn that fails after posting speaks only once.
The guarded commit establishes one admitted turn. The provider takes no part in that commit, so a crash in the middle of a provider call is outside what admission can settle, and anything on the far side of that boundary needs idempotency of its own.
When a signed event comes around again, then, the application shouldn’t ask the queue whether it has seen it, since the queue’s job is to keep repeating itself until someone acknowledges. It asks the session, under the event’s own id, in the same commit that would admit the work, because that is the one place a crash can’t separate the answer from the fact it describes.
Chris Chabot, November 2026