Skip to content

SDK retry audit: idempotency is still missing on spawn, upload, and create/append POSTs #356

Description

@khaliqgant

Summary

#355 closes the concrete actions.invoke() retry defect from #354, but the TypeScript SDK uses the same automatic retry loop for every HTTP method. A source inventory at main + #355 head found other POST surfaces with no idempotency key and no stable client-supplied mutation id. A lost committed response can therefore duplicate work or rotate credentials more than once.

Confirmed high-risk surfaces

SDK surface Engine behavior per attempt Ambiguous retry consequence
relay.agents.spawn() / release() /v1/agents/spawn and /v1/agents/release each call actionEngine.invokeAction(), which mints a fresh invocation id The exact distinct-invocation duplicate class fixed for /actions/:name/invoke remains on these legacy entry points
agent.files.upload() createUpload() generates a new file/storage key and presigned URL, then inserts a pending row Orphan/duplicate pending upload records and URLs
relay.subscriptions.create() createSubscription() generates sub_<id> and inserts an active row Duplicate outbound delivery subscriptions
relay.triggers.create() createTrigger() generates trg_<id> and inserts an enabled row Duplicate action firing
relay.certify.submit() / monitor() creates a new certification id; submit immediately runs the external certification Duplicate stored runs and repeated external work
relay.agents.events.emit() appends a new evt_<id> with the next sequence Duplicate session events
webhook/token create and token rotation methods mint new random ids/secrets or replace a credential Lost-response retry can create an inaccessible resource, return a conflict instead of the committed secret, or rotate more than once

The client call sites are currently in packages/sdk-typescript/src/agent.ts and packages/sdk-typescript/src/relay.ts; the retry loop in src/client.ts retries network exceptions and 429/500/502/503/504 without method gating.

Likely naturally idempotent / lower-risk surfaces

Presence heartbeat/disconnect, membership join/leave, mute/unmute, reactions/read receipts with uniqueness, delivery ack/fail/defer, action completion by invocation id, and action registration by name appear set-like or keyed already. They still need contract tests before being declared safe; this list is triage, not proof.

Required follow-up

  1. Classify every SDK POST as keyed/idempotent, create/append, credential rotation, or intentionally ambiguous.
  2. Reuse the workspace/actor-scoped Idempotency-Key contract for create/append operations, preserving one generated key across retries.
  3. Add stable route-level keys to both legacy agent spawn/release surfaces or route them through the now-idempotent action endpoint.
  4. For credential rotations, either replay the original secret for a bounded key or disable automatic retry and surface an explicit ambiguous outcome.
  5. Add one lost-response MUST-NOT-DUPLICATE regression per behavior class, plus same-key/different-payload conflicts.

Related: #354, #355, AgentWorkforce/relay#1604.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions