Skip to content

Redis cluster support Phase 4 prefactor: split WorkerRunner.runLoop() + minimal RedisStreamOps interface #12

Description

@zhchxiao123

What to build

Python and TS already keep the per-agent_type ctrl-stream reads and the worker's direct control-stream read as two separate code paths. Java's WorkerRunner.runLoop() currently combines them: the streams map passed to a single xreadGroup() call contains both every ctrlStream(agentType) and workerCtrlStream(workerId) at once. One of these belongs to the "agent_type" hash-tag group, the other to the "worker" hash-tag group — they don't share a tag and never will (they're different entities), so this single combined call is fundamentally CROSSSLOT-prone under Cluster no matter what's done to the agent_type side alone.

This is prefactoring work needed before the two-phase XREADGROUP plan (Phase 4 main issue) can be implemented in Java at all: split runLoop() into two independent read paths — one for the agent_type streams (which the main Phase 4 issue will turn into the two-phase scan+block plan), and one for workerCtrlStream(workerId) on its own (blocking or non-blocking, running alongside, not mixed in).

To support the split, add a minimal Redis Stream operations interface (RedisStreamOps) with just enough surface for this: group-create-if-not-exists (idempotent, swallowing BUSYGROUP), a single-stream read supporting both non-blocking (blockMs=null) and blocking (blockMs>0) modes, and ack. WorkerRunner should call this interface instead of Jedis/JedisCluster directly for these operations. The remaining stream operations (xadd, xpendingRange, xclaim) are out of scope here — those land in the Phase 5 issue.

Acceptance criteria

  • runLoop() split into two independent read loops: agent_type streams vs workerCtrlStream(workerId), no longer combined into one xreadGroup() call
  • Minimal RedisStreamOps interface added: xgroupCreateIfNotExists(streamName, groupName), a single-stream read method taking count and an optional blockMs (null = non-blocking, >0 = blocked for that many ms), xack(streamName, groupName, id)
  • StandaloneRedisStreamOps/ClusterRedisStreamOps implementations added (both just directly transpose to Jedis/JedisCluster calls — these are all single-key operations, natively supported by both)
  • WorkerRunner updated to depend on RedisStreamOps for these operations instead of importing Jedis/JedisCluster directly for them
  • Existing worker control-stream behavior (direct routing to a specific worker) unchanged in observable behavior, just running as an independent loop

Blocked by

#8

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions