Skip to content

fix(broker): recover delivery cursor on resume#1242

Merged
willwashburn merged 8 commits into
mainfrom
fix/1240-delivery-cursor-restart
Jul 13, 2026
Merged

fix(broker): recover delivery cursor on resume#1242
willwashburn merged 8 commits into
mainfrom
fix/1240-delivery-cursor-restart

Conversation

@willwashburn

@willwashburn willwashburn commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

  • advertise the negotiated Relaycast delivery-cursor capability
  • seed each resumed mailbox from the authoritative cursor before replay
  • key delivery cursor and dedup state by immutable agent identity while retaining strict gap detection

Verification

  • broker library suite: 669 passed, 4 ignored
  • fleet wire fixtures, formatting, and diff checks passed

Fixes #1240.

Requires AgentWorkforce/relaycast#254 to deploy first. Older engines safely omit the cursor and retain strict fresh-session behavior.

Review in cubic

@willwashburn willwashburn requested a review from khaliqgant as a code owner July 10, 2026 14:44
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f06a62e-7e19-4cd3-a2be-6381c404defa

📥 Commits

Reviewing files that changed from the base of the PR and between b07c8ec and 3df5ab9.

📒 Files selected for processing (16)
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.json
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.md
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.json
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.md
  • .agentworkforce/trajectories/completed/2026-07/traj_sm3f7yqrhz8z/summary.md
  • .agentworkforce/trajectories/completed/2026-07/traj_sm3f7yqrhz8z/trajectory.json
  • CHANGELOG.md
  • crates/broker/src/fleet_wire.rs
  • crates/broker/src/node_control.rs
  • crates/broker/src/runtime/api.rs
  • crates/broker/src/runtime/fleet.rs
  • crates/broker/src/runtime/relaycast_events.rs
  • crates/broker/tests/fixtures/fleet-wire/reply.agent_register.json
  • crates/broker/tests/fleet_wire_fixtures.rs
  • specs/fleet-delivery.md
  • tests/e2e/fleet/fleet-e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • crates/broker/tests/fleet_wire_fixtures.rs
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.md
  • .agentworkforce/trajectories/completed/2026-07/traj_sm3f7yqrhz8z/trajectory.json
  • crates/broker/src/runtime/api.rs
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.json
  • crates/broker/src/runtime/relaycast_events.rs
  • crates/broker/tests/fixtures/fleet-wire/reply.agent_register.json
  • specs/fleet-delivery.md
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.md
  • crates/broker/src/runtime/fleet.rs
  • crates/broker/src/fleet_wire.rs
  • crates/broker/src/node_control.rs

📝 Walkthrough

Walkthrough

The broker negotiates a delivery-cursor capability, receives an authoritative ACK cursor during agent registration, keys delivery state by immutable agent identity, and seeds resumed delivery while preserving compatibility, strict gap detection, and fan-out behavior.

Changes

Fleet cursor recovery

Layer / File(s) Summary
Registration cursor contract
crates/broker/src/fleet_wire.rs, crates/broker/src/node_control.rs, crates/broker/tests/fixtures/...
Registration replies and tokens carry optional delivery_ack_seq, with capability and fixture validation.
Identity-keyed delivery tracking
crates/broker/src/node_control.rs
FleetDeliveryBook tracks cursors by agent_id, handles identity replacement and removal, and preserves sequencing and fan-out deduplication.
Registration and replay seeding
crates/broker/src/runtime/..., tests/e2e/fleet/...
Registration and spawn paths seed authoritative cursors, while delivery outcomes determine whether frames are surfaced, acknowledged, or rejected.
Protocol documentation and completion records
specs/fleet-delivery.md, CHANGELOG.md, .agentworkforce/trajectories/...
Protocol compatibility rules, changelog entries, trajectory records, and compaction reports are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Relaycast
  participant AgentRegistration
  participant FleetDeliveryBook
  participant AgentWorker
  Relaycast->>AgentRegistration: return agent_id and delivery_ack_seq
  AgentRegistration->>FleetDeliveryBook: bind identity and seed cursor
  Relaycast->>AgentWorker: send resumed deliver frame
  AgentWorker->>FleetDeliveryBook: observe frame
  FleetDeliveryBook-->>AgentWorker: approve and report up_to_seq or reject identity/gap
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: khaliqgant

Poem

A rabbit guards the cursor trail,
Through restart winds and replay hail.
By agent ID, the paths stay clear,
Gaps stand firm, while frames draw near.
“Hop onward!” cheers the engineer.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: recovering the broker delivery cursor on resume.
Description check ✅ Passed The description covers the summary and verification, though the template's Test Plan and Screenshots sections are missing.
Linked Issues check ✅ Passed The changes cover cursor seeding, identity-scoped state, strict gap handling, protocol docs, and resume-path tests for #1240.
Out of Scope Changes check ✅ Passed The listed changes stay focused on broker resume/cursor recovery and related docs, with no obvious unrelated code paths introduced.
Docstring Coverage ✅ Passed Docstring coverage is 95.92% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1240-delivery-cursor-restart

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a negotiated server-authoritative cursor handshake during agent registration to recover the Fleet delivery cursor after a broker restart. It introduces the relay:delivery-cursor-v1 capability, includes delivery_ack_seq in agent registration replies, and keys delivery cursors by immutable agent IDs rather than reusable names to prevent cursor inheritance issues. Feedback on the changes suggests optimizing the sequence 0 message observation path in crates/broker/src/node_control.rs by consolidating double lookups on self.agents into a single lookup.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 592 to 605
if deliver.seq == 0 {
let up_to_seq = self.agents.get(&deliver.agent).map_or(0, |c| c.up_to_seq);
let up_to_seq = self
.agents
.get(&deliver.agent_id)
.map_or(0, |c| c.up_to_seq);
if self
.agents
.get(&deliver.agent)
.get(&deliver.agent_id)
.is_some_and(|c| c.seen_msg_ids.contains(&deliver.msg_id))
{
return DeliveryDecision::Duplicate { up_to_seq };
}
return DeliveryDecision::Deliver { up_to_seq };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the hot path of sequence 0 message observation, self.agents.get(&deliver.agent_id) is called twice. We can optimize this to a single lookup to improve performance and efficiency.

        if deliver.seq == 0 {
            if let Some(cursor) = self.agents.get(&deliver.agent_id) {
                let up_to_seq = cursor.up_to_seq;
                if cursor.seen_msg_ids.contains(&deliver.msg_id) {
                    return DeliveryDecision::Duplicate { up_to_seq };
                }
                return DeliveryDecision::Deliver { up_to_seq };
            }
            return DeliveryDecision::Deliver { up_to_seq: 0 };
        }

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 581a4d3a04

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/broker/src/node_control.rs Outdated
Comment on lines 606 to 608
let Some(cursor) = self.agents.get(&deliver.agent_id) else {
return if deliver.seq == 1 {
DeliveryDecision::Deliver { up_to_seq: 1 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard unknown agent IDs before treating seq 1 as fresh

With the cursor map now keyed by deliver.agent_id, a late delivery for a retired identity whose reusable name has already been re-registered misses this map and seq == 1 is accepted as a fresh delivery. The surfacing path still injects by deliver.agent, so in that name-reuse/race scenario the old identity's message can be delivered to the new worker instead of being rejected as stale; please verify the active inventory's agent_id for this name before returning Deliver on a missing cursor.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/broker/src/node_control.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/broker/src/node_control.rs (1)

683-690: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve existing progress when reseeding the same identity.

insert can replace a higher in-memory cursor with a lower registration cursor and clears seen_msg_ids. The next valid delivery is then treated as a gap, and seq-0 frames can produce a regressed ACK. Merge with the existing entry and preserve deduplication history.

Proposed fix
+        if let Some(cursor) = self.agents.get_mut(&agent_id) {
+            cursor.agent_name = agent;
+            cursor.up_to_seq = cursor.up_to_seq.max(up_to_seq);
+            return;
+        }
         self.agents.insert(
             agent_id,
             AgentDeliveryCursor {
                 agent_name: agent,

Add a regression test that seeds 42, commits 43, then reseeds 42 and still accepts 44.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/broker/src/node_control.rs` around lines 683 - 690, Update the agent
registration logic around the self.agents.insert call to merge with an existing
AgentDeliveryCursor for the same agent_id instead of replacing it: retain the
highest up_to_seq and preserve seen_msg_ids, while initializing a new cursor
only when no entry exists. Add a regression test covering seed 42, commit 43,
reseed 42, and successful acceptance of 44.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/broker/src/node_control.rs`:
- Around line 683-690: Update the agent registration logic around the
self.agents.insert call to merge with an existing AgentDeliveryCursor for the
same agent_id instead of replacing it: retain the highest up_to_seq and preserve
seen_msg_ids, while initializing a new cursor only when no entry exists. Add a
regression test covering seed 42, commit 43, reseed 42, and successful
acceptance of 44.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ba617b4-3e6b-481a-abf3-5d9ae2b1ce8b

📥 Commits

Reviewing files that changed from the base of the PR and between 6c2134e and c028f00.

📒 Files selected for processing (6)
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.json
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.md
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.json
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.md
  • crates/broker/src/node_control.rs
  • crates/broker/src/runtime/fleet.rs
✅ Files skipped from review due to trivial changes (3)
  • .agentworkforce/trajectories/compacted/compact_qkzes8r7n4br_2026-07-11.md
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.json
  • .agentworkforce/trajectories/compacted/compact_7tjqdu0n483c_2026-07-11.md

@willwashburn willwashburn force-pushed the fix/1240-delivery-cursor-restart branch from c028f00 to 388f454 Compare July 11, 2026 13:42
github-actions Bot and others added 2 commits July 11, 2026 13:42
Resolve conflicts from the #1239 node-provider refactor while preserving
the #1240 delivery-cursor recovery fix:

- node_control.rs: adopt new FleetCapability { global, queue } and
  NodeRegister { provider } fields on the delivery-cursor capability the
  broker advertises; keep identity-keyed cursor seeding.
- runtime/fleet.rs: keep FleetDeliveryPlan / plan_fleet_delivery and the
  identity-reject delivery path; drop the FleetSidecarRestartState and
  fleet spawn helpers that #1239 removed (along with their tests); take
  main's node_control import surface plus DeliveryDecision.
- CHANGELOG.md: keep both Unreleased Fixed bullets alongside main's.

Broker lib suite green (672 passed), clippy + fmt clean; all delivery
cursor / authoritative-identity tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5g6BDS2YxXMQ3X3Fcq5Nf
@willwashburn willwashburn force-pushed the fix/1240-delivery-cursor-restart branch from b07c8ec to 6fec797 Compare July 13, 2026 14:59
claude added 2 commits July 13, 2026 15:05
The merge landed the #1240 delivery-cursor bullet under the released
9.2.2 section (git matched surrounding context that main had since
moved into published releases). The fix is unshipped, so move it to
[Unreleased] > Fixed and restore 9.2.2 to main's version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5g6BDS2YxXMQ3X3Fcq5Nf
The broker now appends the relay:delivery-cursor-v1 capacity marker to
its NodeRegister (the #1240 restart-safe mailbox resume negotiation), so
both nodes' aggregate capability lists include it. Update the boot/register
scenario's expected capability objects to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5g6BDS2YxXMQ3X3Fcq5Nf
@willwashburn willwashburn merged commit 8625a72 into main Jul 13, 2026
40 checks passed
@willwashburn willwashburn deleted the fix/1240-delivery-cursor-restart branch July 13, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0] Broker restart resets delivery cursor and blocks resumed agent mailboxes

2 participants