Hi @perryraskin -
Read the workflow and the Eve channel.
process has maxRetries = 4 (src/workflows/imessage-event.ts:22). Its last action is dispatchToAgent, which gives up after 20 s (src/lib/imessage/agent.server.ts:28) or on any non-ok response (line 31). If Eve has already accepted and queued the turn when the timeout or a transient 5xx hits, the retry POSTs the same eventId again. The Eve route (agent/channels/imessage.ts:77-103) does not check eventId; it calls from(conversationId).send(...) with turnPolicy: "queue" and starts a second turn. Tool idempotency keys derive from the turn id (agent/lib/saas-internal.ts:48), so the second turn's writes are new writes, and the user gets two replies.
The once-per-event claim (imessage-event.ts:26) does not cover this because it runs before the step that is retried.
Suggested fix, either side works:
- In the Eve route, keep a short-lived set of seen
eventIds (or ask the session whether a turn with thateventIdin its state already exists) and return202with the existing session id instead of sending again. - Or base the tool idempotency keys on
eventIdrather than the turn id, so a duplicate turn's writes collapse into the first.
The sealing, constant-time secret compare and per-send idempotency keys all looked right; this was the one path where a retry changes the outcome.
Activity
0No activity yet.