Skip to content

Redis: atomic session creation and appendMessages on a missing session ​

Overview ​

@helix-agents/store-redis makes two session writes atomic (RM-50):

  • createSession is one Lua script. It used to claim the session with HSETNX and write status and the other fields in a second round trip, so a concurrent loadState could see a { sessionId }-only hash and throw InvalidStoredStateError ("Invalid stored status … received undefined"). Now a reader sees either no session or the whole one. Nothing changes for callers: duplicate creation still throws Session already exists: <id>.
  • A recreated session starts clean. Some keys can outlive a session hash with the same id: they carry separate TTLs, or were left by a crash or by the old appendMessages. createSession now clears the leftovers: custom state, the message list, a status-less hash (and its messageCount), the run indexes, the sub-session ids and the checkpoint index. It deliberately keeps an interrupt flag, since an interrupt can be requested before a durable runtime creates the session.
  • appendMessages is one Lua script: the message append and the messageCount bump apply together, and only to an existing session.

Breaking change: appendMessages on a missing session throws ​

RedisStateStore.appendMessages(sessionId, messages) now throws Session not found: <sessionId> when the session does not exist, and writes nothing. This is the documented SessionStateStore contract (@throws Error if session not found) and what the in-memory and Postgres stores already do (D1 and the Durable Object store throw StateNotFoundError). The shared store contract suite now checks it for every store.

Before, Redis silently wrote an orphan message list and a status-less { messageCount } session hash. The next loadState for that id then threw InvalidStoredStateError, and a later createSession of the same id inherited the orphan messages.

Who is affected: only code that appends to a session it never created, or one that was deleted or expired. The built-in runtimes create (or clone) a session before appending to it. If your own code relies on appending first, call createSession first.

appendMessages now also refreshes the session hash's TTL along with the message list's, so a session that is only appended to for longer than defaultTTL no longer loses its hash while its history grows.

Deploy notes ​

No data migration and no ordering requirement. Mixed old/new writers are safe: both claim a session on the same sessionId hash field. A status-less { messageCount } hash written by an old appendMessages before the upgrade is still rejected by loadState, as it was before; creating that session id again replaces it with a complete session.

Released under the MIT License.